import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/entities/contribution.dart'; import '../../domain/entities/contribution_record.dart'; import '../../domain/entities/contribution_stats.dart'; import '../../domain/usecases/contribution/get_user_contribution.dart'; import '../../domain/repositories/contribution_repository.dart'; import '../../core/di/injection.dart'; final getUserContributionUseCaseProvider = Provider((ref) { return getIt(); }); final contributionRepositoryProvider = Provider((ref) { return getIt(); }); final contributionProvider = FutureProvider.family( (ref, accountSequence) async { // 空字符串不请求 if (accountSequence.isEmpty) return null; final useCase = ref.watch(getUserContributionUseCaseProvider); final result = await useCase(accountSequence); // 保持 provider 活跃,避免重复请求 ref.keepAlive(); // 5 分钟后自动失效 final timer = Timer(const Duration(minutes: 5), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (contribution) => contribution, ); }, ); /// 贡献值记录请求参数 class ContributionRecordsParams { final String accountSequence; final int page; final int pageSize; const ContributionRecordsParams({ required this.accountSequence, this.page = 1, this.pageSize = 10, }); @override bool operator ==(Object other) => identical(this, other) || other is ContributionRecordsParams && runtimeType == other.runtimeType && accountSequence == other.accountSequence && page == other.page && pageSize == other.pageSize; @override int get hashCode => accountSequence.hashCode ^ page.hashCode ^ pageSize.hashCode; } /// 贡献值记录 Provider final contributionRecordsProvider = FutureProvider.family( (ref, params) async { // 空字符串不请求 if (params.accountSequence.isEmpty) { return null; } final repository = ref.watch(contributionRepositoryProvider); final result = await repository.getContributionRecords( params.accountSequence, page: params.page, pageSize: params.pageSize, ); // 保持 provider 活跃 ref.keepAlive(); // 5 分钟后自动失效 final timer = Timer(const Duration(minutes: 5), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => throw Exception(failure.message), (records) => records, ); }, ); /// 全网贡献值统计 Provider final contributionStatsProvider = FutureProvider((ref) async { final repository = ref.watch(contributionRepositoryProvider); final result = await repository.getContributionStats(); // 保持 provider 活跃 ref.keepAlive(); // 5 分钟后自动失效 final timer = Timer(const Duration(minutes: 5), () { ref.invalidateSelf(); }); ref.onDispose(() => timer.cancel()); return result.fold( (failure) => null, (stats) => stats, ); }); /// 计算用户今日预估收益 /// 公式: (用户贡献值 / 全网总贡献值) × 每日发放量 /// 每日发放量 = 每秒发放量 × 86400 class EstimatedEarnings { final String dailyShares; final String perSecondShares; final bool isValid; const EstimatedEarnings({ required this.dailyShares, required this.perSecondShares, required this.isValid, }); static const zero = EstimatedEarnings( dailyShares: '0', perSecondShares: '0', isValid: false, ); } /// 每日发放总量配置(积分股) /// 第1纪元: 100M / (2年 × 365天) ≈ 136,986 积分股/天 const double dailyAllocationTotal = 136986.0; final estimatedEarningsProvider = Provider.family((ref, accountSequence) { final contributionAsync = ref.watch(contributionProvider(accountSequence)); final statsAsync = ref.watch(contributionStatsProvider); final contribution = contributionAsync.valueOrNull; final stats = statsAsync.valueOrNull; if (contribution == null || stats == null) { return EstimatedEarnings.zero; } final userContribution = double.tryParse(contribution.totalContribution) ?? 0; final totalContribution = double.tryParse(stats.totalContribution) ?? 0; if (totalContribution <= 0 || userContribution <= 0) { return EstimatedEarnings.zero; } // 用户占比 final ratio = userContribution / totalContribution; // 每日预估 final dailyShares = ratio * dailyAllocationTotal; // 每秒预估 final perSecondShares = dailyShares / 86400; return EstimatedEarnings( dailyShares: dailyShares.toStringAsFixed(4), perSecondShares: perSecondShares.toStringAsFixed(8), isValid: true, ); });