import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../../core/constants/app_colors.dart'; import '../../../core/utils/format_utils.dart'; import '../../../domain/entities/contribution.dart'; import '../../../domain/entities/contribution_record.dart'; import '../../providers/user_providers.dart'; import '../../providers/contribution_providers.dart'; import '../../widgets/shimmer_loading.dart'; class ContributionPage extends ConsumerWidget { const ContributionPage({super.key}); // 设计色彩 static const Color _orange = Color(0xFFFF6B00); static const Color _green = Color(0xFF22C55E); static const Color _grayText = Color(0xFF6B7280); static const Color _darkText = Color(0xFF1F2937); static const Color _bgGray = Color(0xFFF3F4F6); static const Color _lightGray = Color(0xFFF9FAFB); @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userNotifierProvider); final accountSequence = user.accountSequence ?? ''; final contributionAsync = ref.watch(contributionProvider(accountSequence)); final recordsParams = ContributionRecordsParams( accountSequence: accountSequence, page: 1, pageSize: 3, ); final recordsAsync = ref.watch(contributionRecordsProvider(recordsParams)); // Extract loading state and data from AsyncValue final isLoading = contributionAsync.isLoading; final contribution = contributionAsync.valueOrNull; final hasError = contributionAsync.hasError; final error = contributionAsync.error; return Scaffold( backgroundColor: const Color(0xFFF5F5F5), body: SafeArea( bottom: false, child: RefreshIndicator( onRefresh: () async { ref.invalidate(contributionProvider(accountSequence)); ref.invalidate(contributionRecordsProvider(recordsParams)); }, child: hasError && contribution == null ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: AppColors.error), const SizedBox(height: 16), Text('加载失败: $error'), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.invalidate(contributionProvider(accountSequence)), child: const Text('重试'), ), ], ), ) : CustomScrollView( slivers: [ // 顶部导航栏 SliverToBoxAdapter(child: _buildAppBar(context)), // 内容 SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildListDelegate([ // 总贡献值卡片 _buildTotalContributionCard(contribution, isLoading), const SizedBox(height: 16), // 三栏统计 _buildThreeColumnStats(contribution, isLoading), const SizedBox(height: 16), // 今日预估收益 _buildTodayEstimateCard(contribution, isLoading), const SizedBox(height: 16), // 贡献值明细 _buildContributionDetailCard(context, ref, recordsAsync), const SizedBox(height: 16), // 团队层级统计 _buildTeamStatsCard(contribution, isLoading), const SizedBox(height: 16), // 贡献值失效倒计时 _buildExpirationCard(contribution, recordsAsync, isLoading), const SizedBox(height: 100), ]), ), ), ], ), ), ), ); } Widget _buildAppBar(BuildContext context) { return Container( color: _lightGray, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ // Logo Container( width: 32, height: 32, decoration: BoxDecoration( color: _orange.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.eco, color: _orange, size: 20), ), const SizedBox(width: 8), const Text( '榴莲生态', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: _darkText, letterSpacing: 0.45, ), ), const Spacer(), // 客服 IconButton( icon: const Icon(Icons.headset_mic_outlined, color: _grayText), onPressed: () {}, ), // 通知(带红点) Stack( children: [ IconButton( icon: const Icon(Icons.notifications_outlined, color: _grayText), onPressed: () {}, ), Positioned( right: 10, top: 10, child: Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ), ], ), ], ), ); } Widget _buildTotalContributionCard(Contribution? contribution, bool isLoading) { final total = contribution?.effectiveContribution ?? '0'; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '总贡献值', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: _grayText), ), Icon(Icons.visibility_outlined, color: _grayText.withOpacity(0.5), size: 18), ], ), const SizedBox(height: 8), DataText( data: isLoading ? null : formatAmount(total), isLoading: isLoading, placeholder: '----', style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: _orange, letterSpacing: -0.75, ), ), const SizedBox(height: 12), // 有效期标签 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _lightGray, borderRadius: BorderRadius.circular(999), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.info_outline, size: 14, color: _grayText.withOpacity(0.7)), const SizedBox(width: 6), Text( '贡献值有效期: 730 天', style: TextStyle(fontSize: 12, color: _grayText.withOpacity(0.9)), ), ], ), ), ], ), ); } Widget _buildThreeColumnStats(Contribution? contribution, bool isLoading) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Row( children: [ _buildStatColumn('个人贡献值', contribution?.personalContribution, isLoading, false), _buildStatColumn('团队贡献值', contribution?.teamLevelContribution, isLoading, true), _buildStatColumn('省市公司', contribution?.systemContribution, isLoading, true), ], ), ); } Widget _buildStatColumn(String label, String? value, bool isLoading, bool showLeftBorder) { return Expanded( child: Container( decoration: showLeftBorder ? const BoxDecoration( border: Border(left: BorderSide(color: Color(0xFFE5E7EB), width: 1)), ) : null, padding: const EdgeInsets.symmetric(horizontal: 4), child: Column( children: [ Text(label, style: const TextStyle(fontSize: 12, color: _grayText)), const SizedBox(height: 4), DataText( data: value != null ? formatAmount(value) : null, isLoading: isLoading, placeholder: '--', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _darkText), textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildTodayEstimateCard(Contribution? contribution, bool isLoading) { // 基于贡献值计算预估收益(暂时显示占位符,后续可接入实际计算API) final effectiveContribution = double.tryParse(contribution?.effectiveContribution ?? '0') ?? 0; // 简单估算:假设每日发放总量为 10000 积分股,按贡献值占比分配 // 这里先显示"--"表示暂无数据,后续可接入实际计算 final hasContribution = effectiveContribution > 0; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Row( children: [ // 图标 Container( width: 40, height: 40, decoration: BoxDecoration( color: _green.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: const Icon(Icons.trending_up, color: _green, size: 24), ), const SizedBox(width: 12), // 文字说明 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '今日预估收益', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: _grayText), ), Text( '基于当前贡献值占比计算', style: TextStyle(fontSize: 12, color: _grayText.withOpacity(0.7)), ), ], ), ), // 收益数值 Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ isLoading ? const ShimmerText( placeholder: '-- 积分股', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _green), ) : Text.rich( TextSpan( children: [ TextSpan( text: hasContribution ? '计算中' : '--', style: TextStyle( fontSize: hasContribution ? 14 : 18, fontWeight: FontWeight.bold, color: _green, ), ), const TextSpan( text: ' 积分股', style: TextStyle(fontSize: 12, color: _green), ), ], ), ), ], ), ], ), ); } Widget _buildContributionDetailCard( BuildContext context, WidgetRef ref, AsyncValue recordsAsync, ) { final isRecordsLoading = recordsAsync.isLoading; final recordsPage = recordsAsync.valueOrNull; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( children: [ // 标题行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '贡献值明细', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _darkText), ), GestureDetector( onTap: () { // TODO: 跳转到完整记录页面 }, child: const Row( children: [ Text('查看全部', style: TextStyle(fontSize: 12, color: _orange)), Icon(Icons.chevron_right, size: 14, color: _orange), ], ), ), ], ), const SizedBox(height: 16), // 明细列表 if (isRecordsLoading) _buildRecordsShimmer() else if (recordsAsync.hasError && recordsPage == null) Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Text( '加载失败', style: TextStyle(fontSize: 14, color: _grayText.withOpacity(0.7)), ), ) else if (recordsPage == null || recordsPage.data.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text( '暂无贡献值记录', style: TextStyle(fontSize: 14, color: _grayText), ), ) else Column( children: recordsPage.data.asMap().entries.map((entry) { final index = entry.key; final record = entry.value; return Column( children: [ _buildDetailRow(record), if (index < recordsPage.data.length - 1) const Divider(height: 24), ], ); }).toList(), ), ], ), ); } Widget _buildRecordsShimmer() { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Column( children: [ _buildShimmerDetailRow(), const Divider(height: 24), _buildShimmerDetailRow(), const Divider(height: 24), _buildShimmerDetailRow(), ], ), ); } Widget _buildShimmerDetailRow() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ ShimmerText( placeholder: '认种贡献', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: _darkText), ), SizedBox(height: 2), ShimmerText( placeholder: '2024-01-01 12:00', style: TextStyle(fontSize: 12, color: _grayText), ), ], ), const ShimmerText( placeholder: '+1,000', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _green), ), ], ); } Widget _buildDetailRow(ContributionRecord record) { final dateFormat = DateFormat('yyyy-MM-dd HH:mm'); final formattedDate = dateFormat.format(record.createdAt); final amount = '+${formatAmount(record.finalContribution)}'; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( record.displayTitle, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: _darkText), ), const SizedBox(height: 2), Text(formattedDate, style: const TextStyle(fontSize: 12, color: _grayText)), ], ), Text( amount, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _green), ), ], ); } Widget _buildTeamStatsCard(Contribution? contribution, bool isLoading) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '团队层级统计', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _darkText), ), const SizedBox(height: 16), // 第一行 Row( children: [ _buildTeamStatItem( '直推人数', contribution?.directReferralAdoptedCount.toString(), '人', isLoading, ), const SizedBox(width: 16), _buildTeamStatItem( '已解锁奖励', contribution?.unlockedBonusTiers.toString(), '档', isLoading, ), ], ), const SizedBox(height: 16), // 第二行 Row( children: [ _buildTeamStatItem( '已解锁层级', contribution?.unlockedLevelDepth.toString(), '级', isLoading, ), const SizedBox(width: 16), _buildTeamStatItem( '是否认种', contribution != null ? (contribution.hasAdopted == true ? '是' : '否') : null, '', isLoading, ), ], ), ], ), ); } Widget _buildTeamStatItem(String label, String? value, String unit, bool isLoading) { return Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 12, color: _grayText)), const SizedBox(height: 4), isLoading ? ShimmerText( placeholder: unit.isNotEmpty ? '-- $unit' : '--', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: _orange), ) : Text.rich( TextSpan( children: [ TextSpan( text: '${value ?? '0'} ', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: _orange), ), if (unit.isNotEmpty) TextSpan( text: unit, style: const TextStyle(fontSize: 12, color: _grayText), ), ], ), ), ], ), ), ); } Widget _buildExpirationCard( Contribution? contribution, AsyncValue recordsAsync, bool isLoading, ) { final isRecordsLoading = recordsAsync.isLoading; final recordsPage = recordsAsync.valueOrNull; // 从记录中获取最近的过期日期 DateTime? nearestExpireDate; if (recordsPage != null && recordsPage.data.isNotEmpty) { // 找到未过期记录中最近的过期日期 final activeRecords = recordsPage.data.where((r) => !r.isExpired).toList(); if (activeRecords.isNotEmpty) { activeRecords.sort((a, b) => a.expireDate.compareTo(b.expireDate)); nearestExpireDate = activeRecords.first.expireDate; } } // 计算剩余天数和进度 final now = DateTime.now(); int daysRemaining = 730; // 默认值 double progress = 1.0; String expireDateText = '暂无过期信息'; if (nearestExpireDate != null) { daysRemaining = nearestExpireDate.difference(now).inDays; if (daysRemaining < 0) daysRemaining = 0; // 假设总有效期为730天 progress = daysRemaining / 730; if (progress > 1) progress = 1; if (progress < 0) progress = 0; expireDateText = '您的贡献值将于 ${DateFormat('yyyy-MM-dd').format(nearestExpireDate)} 失效'; } final showShimmer = isLoading || isRecordsLoading; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题 const Row( children: [ Icon(Icons.timer_outlined, color: _orange, size: 24), SizedBox(width: 8), Text( '贡献值失效倒计时', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _darkText), ), ], ), const SizedBox(height: 12), // 进度条 ClipRRect( borderRadius: BorderRadius.circular(5), child: LinearProgressIndicator( value: showShimmer ? 1.0 : progress, minHeight: 10, backgroundColor: _bgGray, valueColor: AlwaysStoppedAnimation( showShimmer ? _bgGray : _orange, ), ), ), const SizedBox(height: 12), // 说明文字 showShimmer ? const ShimmerText( placeholder: '您的贡献值将于 ---- 失效', style: TextStyle(fontSize: 12, color: _grayText), ) : Text( expireDateText, style: const TextStyle(fontSize: 12, color: _grayText), ), const SizedBox(height: 4), showShimmer ? const ShimmerText( placeholder: '剩余 --- 天', style: TextStyle(fontSize: 12, color: _orange, fontWeight: FontWeight.w500), ) : Text( '剩余 $daysRemaining 天', style: const TextStyle(fontSize: 12, color: _orange, fontWeight: FontWeight.w500), ), const SizedBox(height: 8), // 提示 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(4), ), child: const Text( '* 运营账号贡献值永不失效', style: TextStyle(fontSize: 10, color: _orange), ), ), ], ), ); } }