- 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
181 lines
6.5 KiB
Dart
181 lines
6.5 KiB
Dart
// lib/features/gamification/services/gamification_service.dart
|
|
//
|
|
// Stub service using the real API contract from TechDocs v2.
|
|
// All methods currently return mock data.
|
|
// TODO: replace each mock block with a real ApiClient call once
|
|
// the backend endpoints are live on uat.eventifyplus.com.
|
|
|
|
import 'dart:math';
|
|
import '../models/gamification_models.dart';
|
|
|
|
class GamificationService {
|
|
// ---------------------------------------------------------------------------
|
|
// User Gamification Profile
|
|
// TODO: replace with ApiClient.get(ApiEndpoints.gamificationProfile)
|
|
// ---------------------------------------------------------------------------
|
|
Future<UserGamificationProfile> getProfile() async {
|
|
await Future.delayed(const Duration(milliseconds: 400));
|
|
return const UserGamificationProfile(
|
|
userId: 'mock-user-001',
|
|
lifetimeEp: 320,
|
|
currentEp: 70,
|
|
currentRp: 45,
|
|
tier: ContributorTier.SILVER,
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Leaderboard
|
|
// district: 'Overall Kerala' | 'Thiruvananthapuram' | 'Kollam' | ...
|
|
// timePeriod: 'all_time' | 'this_month'
|
|
// TODO: replace with ApiClient.get(ApiEndpoints.leaderboard, params: {'district': district, 'period': timePeriod})
|
|
// ---------------------------------------------------------------------------
|
|
Future<List<LeaderboardEntry>> getLeaderboard({
|
|
required String district,
|
|
required String timePeriod,
|
|
}) async {
|
|
await Future.delayed(const Duration(milliseconds: 500));
|
|
|
|
// Realistic mock names per district
|
|
final names = [
|
|
'Annette Black', 'Jerome Bell', 'Theresa Webb', 'Courtney Henry',
|
|
'Cameron Williamson', 'Dianne Russell', 'Wade Warren', 'Albert Flores',
|
|
'Kristin Watson', 'Guy Hawkins',
|
|
];
|
|
|
|
final rng = Random(district.hashCode ^ timePeriod.hashCode);
|
|
final baseEp = timePeriod == 'this_month' ? 800 : 4500;
|
|
|
|
final entries = List.generate(10, (i) {
|
|
final ep = baseEp - (i * (timePeriod == 'this_month' ? 55 : 280)) + rng.nextInt(30);
|
|
return LeaderboardEntry(
|
|
rank: i + 1,
|
|
username: names[i],
|
|
lifetimeEp: ep,
|
|
tier: tierFromEp(ep),
|
|
eventsCount: 149 - i * 12,
|
|
isCurrentUser: i == 7, // mock: current user is rank 8
|
|
);
|
|
});
|
|
|
|
return entries;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Redeem Shop Items
|
|
// TODO: replace with ApiClient.get(ApiEndpoints.shopItems)
|
|
// ---------------------------------------------------------------------------
|
|
Future<List<ShopItem>> getShopItems() async {
|
|
await Future.delayed(const Duration(milliseconds: 400));
|
|
return const [
|
|
ShopItem(
|
|
id: 'item-001',
|
|
name: 'Amazon ₹500 Voucher',
|
|
description: 'Redeem for any purchase on Amazon India.',
|
|
rpCost: 50,
|
|
stockQuantity: 20,
|
|
),
|
|
ShopItem(
|
|
id: 'item-002',
|
|
name: 'Swiggy ₹200 Voucher',
|
|
description: 'Free food delivery credit on Swiggy.',
|
|
rpCost: 20,
|
|
stockQuantity: 35,
|
|
),
|
|
ShopItem(
|
|
id: 'item-003',
|
|
name: 'Eventify Pro — 1 Month',
|
|
description: 'Premium access to Eventify.Plus features.',
|
|
rpCost: 30,
|
|
stockQuantity: 100,
|
|
),
|
|
ShopItem(
|
|
id: 'item-004',
|
|
name: 'Zomato ₹150 Voucher',
|
|
description: 'Discount on your next Zomato order.',
|
|
rpCost: 15,
|
|
stockQuantity: 50,
|
|
),
|
|
ShopItem(
|
|
id: 'item-005',
|
|
name: 'BookMyShow ₹300 Voucher',
|
|
description: 'Movie & event ticket credit on BookMyShow.',
|
|
rpCost: 30,
|
|
stockQuantity: 15,
|
|
),
|
|
ShopItem(
|
|
id: 'item-006',
|
|
name: 'Exclusive Badge',
|
|
description: 'Rare "Pioneer" badge for your profile.',
|
|
rpCost: 5,
|
|
stockQuantity: 0, // out of stock
|
|
),
|
|
];
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Redeem an item
|
|
// TODO: replace with ApiClient.post(ApiEndpoints.shopRedeem, body: {'item_id': itemId})
|
|
// ---------------------------------------------------------------------------
|
|
Future<RedemptionRecord> redeemItem(String itemId) async {
|
|
await Future.delayed(const Duration(milliseconds: 600));
|
|
// Generate a fake voucher code
|
|
final code = 'EVT-${itemId.toUpperCase().replaceAll('-', '').substring(0, 4)}-${Random().nextInt(9000) + 1000}';
|
|
return RedemptionRecord(
|
|
id: 'redemption-${DateTime.now().millisecondsSinceEpoch}',
|
|
itemId: itemId,
|
|
rpSpent: 0, // provider will look up cost
|
|
voucherCode: code,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Submit Contribution
|
|
// TODO: replace with ApiClient.post(ApiEndpoints.contributeSubmit, body: data)
|
|
// ---------------------------------------------------------------------------
|
|
Future<void> submitContribution(Map<String, dynamic> data) async {
|
|
await Future.delayed(const Duration(milliseconds: 800));
|
|
// Mock always succeeds
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Achievements
|
|
// ---------------------------------------------------------------------------
|
|
Future<List<AchievementBadge>> getAchievements() async {
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
|
return const [
|
|
AchievementBadge(
|
|
id: 'badge-01', title: 'First Submission',
|
|
description: 'Submitted your first event.',
|
|
iconName: 'edit', isUnlocked: true, progress: 1.0,
|
|
),
|
|
AchievementBadge(
|
|
id: 'badge-02', title: 'Silver Streak',
|
|
description: 'Reached Silver tier.',
|
|
iconName: 'star', isUnlocked: true, progress: 1.0,
|
|
),
|
|
AchievementBadge(
|
|
id: 'badge-03', title: 'Gold Rush',
|
|
description: 'Reach Gold tier (500 EP).',
|
|
iconName: 'emoji_events', isUnlocked: false, progress: 0.64,
|
|
),
|
|
AchievementBadge(
|
|
id: 'badge-04', title: 'Top 10',
|
|
description: 'Appear in the district leaderboard top 10.',
|
|
iconName: 'leaderboard', isUnlocked: false, progress: 0.5,
|
|
),
|
|
AchievementBadge(
|
|
id: 'badge-05', title: 'Image Pro',
|
|
description: 'Submit 10 events with 3+ images.',
|
|
iconName: 'photo_library', isUnlocked: false, progress: 0.3,
|
|
),
|
|
AchievementBadge(
|
|
id: 'badge-06', title: 'Pioneer',
|
|
description: 'One of the first 100 contributors.',
|
|
iconName: 'verified', isUnlocked: true, progress: 1.0,
|
|
),
|
|
];
|
|
}
|
|
}
|