1. 修复 Light Mode 卡片背景色(surfaceCard: #FFFFFF) 2. 修复首页充值按钮颜色(使用主色实心背景+白色文字) 3. 修复'更多'和'资产详情'文字颜色(使用 text-muted) 4. 修复邀请码卡片背景色(使用琥珀色渐变) 5. 修复'复制邀请码'按钮文字颜色(使用 onPrimary)
644 lines
20 KiB
Dart
644 lines
20 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||
import 'package:provider/provider.dart';
|
||
import '../../../core/theme/app_spacing.dart';
|
||
import '../../../core/theme/app_theme.dart';
|
||
import '../../../core/theme/app_theme_extension.dart';
|
||
import '../../../core/theme/app_color_scheme.dart';
|
||
import '../../../core/utils/toast_utils.dart';
|
||
import '../../../core/event/app_event_bus.dart';
|
||
import '../../../data/services/bonus_service.dart';
|
||
import '../../../providers/asset_provider.dart';
|
||
|
||
/// 福利中心页面
|
||
class WelfareCenterPage extends StatefulWidget {
|
||
const WelfareCenterPage({super.key});
|
||
|
||
@override
|
||
State<WelfareCenterPage> createState() => _WelfareCenterPageState();
|
||
}
|
||
|
||
class _WelfareCenterPageState extends State<WelfareCenterPage> {
|
||
Map<String, dynamic>? _welfareData;
|
||
bool _isLoading = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
|
||
}
|
||
|
||
Future<void> _loadData() async {
|
||
try {
|
||
final bonusService = context.read<BonusService>();
|
||
final response = await bonusService.getWelfareStatus();
|
||
if (mounted) {
|
||
setState(() {
|
||
_welfareData = response.data;
|
||
_isLoading = false;
|
||
});
|
||
}
|
||
} catch (_) {
|
||
if (mounted) setState(() => _isLoading = false);
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// 容器样式辅助
|
||
// ============================================
|
||
|
||
/// 标准卡片容器
|
||
BoxDecoration _cardDecoration({Color? borderColor}) {
|
||
return BoxDecoration(
|
||
color: context.appColors.surfaceCard,
|
||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||
border: Border.all(
|
||
color: borderColor ?? context.appColors.ghostBorder,
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 金色渐变卡片容器
|
||
BoxDecoration _goldGradientDecoration() {
|
||
final isDark = context.isDark;
|
||
// Light Mode: 琥珀色渐变(从 amber 15% 到深灰 5%)
|
||
// Dark Mode: 金色渐变
|
||
final gradientColors = isDark
|
||
? [
|
||
context.appColors.accentPrimary.withValues(alpha: 0.15),
|
||
context.appColors.surfaceCard,
|
||
]
|
||
: [
|
||
const Color(0xFFF59E0B).withValues(alpha: 0.15), // amber-500 with 15% opacity
|
||
const Color(0xFF1F2937).withValues(alpha: 0.05), // gray-800 with 5% opacity
|
||
];
|
||
|
||
return BoxDecoration(
|
||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||
gradient: LinearGradient(
|
||
begin: Alignment.topCenter,
|
||
end: Alignment.bottomCenter,
|
||
colors: gradientColors,
|
||
),
|
||
border: Border.all(
|
||
color: isDark
|
||
? context.appColors.accentPrimary.withValues(alpha: 0.3)
|
||
: const Color(0xFFF59E0B).withValues(alpha: 0.3),
|
||
width: 1,
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 状态胶囊标签
|
||
Widget _statusBadge(String text, Color textColor, Color bgColor) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: bgColor,
|
||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||
),
|
||
child: Text(
|
||
text,
|
||
style: AppTextStyles.labelSmall(context).copyWith(color: textColor),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 全宽按钮
|
||
Widget _fullWidthButton({
|
||
required String text,
|
||
required Color backgroundColor,
|
||
required Color foregroundColor,
|
||
required VoidCallback? onPressed,
|
||
Color? disabledBackgroundColor,
|
||
}) {
|
||
return SizedBox(
|
||
width: double.infinity,
|
||
height: 44,
|
||
child: ElevatedButton(
|
||
onPressed: onPressed,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: backgroundColor,
|
||
foregroundColor: foregroundColor,
|
||
elevation: 0,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||
),
|
||
disabledBackgroundColor:
|
||
disabledBackgroundColor ?? backgroundColor.withValues(alpha: 0.3),
|
||
disabledForegroundColor: foregroundColor.withValues(alpha: 0.7),
|
||
),
|
||
child: Text(
|
||
text,
|
||
style: AppTextStyles.headlineMedium(context).copyWith(
|
||
fontWeight: FontWeight.w700,
|
||
color: foregroundColor,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final goldAccent = context.appColors.accentPrimary;
|
||
|
||
return Scaffold(
|
||
backgroundColor: context.colors.surface,
|
||
appBar: AppBar(
|
||
backgroundColor: context.colors.surface,
|
||
elevation: 0,
|
||
scrolledUnderElevation: 0,
|
||
centerTitle: false,
|
||
titleSpacing: 0,
|
||
title: Text(
|
||
'福利中心',
|
||
style: AppTextStyles.headlineLarge(context),
|
||
),
|
||
leading: IconButton(
|
||
icon: Icon(LucideIcons.arrowLeft, color: context.colors.onSurface, size: 24),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
),
|
||
body: _isLoading
|
||
? Center(
|
||
child: CircularProgressIndicator(
|
||
color: goldAccent,
|
||
strokeWidth: 2.5,
|
||
),
|
||
)
|
||
: RefreshIndicator(
|
||
onRefresh: _loadData,
|
||
color: goldAccent,
|
||
child: SingleChildScrollView(
|
||
physics: const AlwaysScrollableScrollPhysics(),
|
||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_buildReferralCodeCard(context),
|
||
const SizedBox(height: 16),
|
||
_buildNewUserBonusCard(context),
|
||
const SizedBox(height: 16),
|
||
_buildReferralRewardsSection(context),
|
||
const SizedBox(height: 16),
|
||
_buildRulesCard(context),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================
|
||
// 推广码卡片(金色渐变边框 Hero Card)
|
||
// ============================================
|
||
|
||
Widget _buildReferralCodeCard(BuildContext context) {
|
||
final goldAccent = context.appColors.accentPrimary;
|
||
final referralCode = _welfareData?['referralCode'] as String? ?? '';
|
||
|
||
return Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.all(24),
|
||
decoration: _goldGradientDecoration(),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Header Row: gift icon + 标题
|
||
Row(
|
||
children: [
|
||
Icon(LucideIcons.gift, color: goldAccent, size: 24),
|
||
const SizedBox(width: 10),
|
||
Text(
|
||
'我的邀请码',
|
||
style: AppTextStyles.headlineLarge(context).copyWith(
|
||
fontWeight: FontWeight.w700,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
referralCode.isEmpty ? '暂无邀请码' : referralCode,
|
||
style: AppTextStyles.displayMedium(context).copyWith(
|
||
fontWeight: FontWeight.w800,
|
||
color: goldAccent,
|
||
letterSpacing: 2,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
SizedBox(
|
||
width: double.infinity,
|
||
height: 40,
|
||
child: ElevatedButton(
|
||
onPressed: referralCode.isEmpty
|
||
? null
|
||
: () {
|
||
Clipboard.setData(ClipboardData(text: referralCode));
|
||
ToastUtils.show('邀请码已复制');
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: goldAccent,
|
||
foregroundColor: context.colors.onPrimary,
|
||
elevation: 0,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||
),
|
||
disabledBackgroundColor: goldAccent.withValues(alpha: 0.4),
|
||
),
|
||
child: Text(
|
||
'复制邀请码',
|
||
style: AppTextStyles.headlineMedium(context).copyWith(color: context.colors.onPrimary),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================
|
||
// 新人福利卡片
|
||
// ============================================
|
||
|
||
Widget _buildNewUserBonusCard(BuildContext context) {
|
||
final profitGreen = context.appColors.up;
|
||
final profitGreenBg = context.appColors.upBackground;
|
||
final newUserBonus = _welfareData?['newUserBonus'] as Map<String, dynamic>?;
|
||
final eligible = newUserBonus?['eligible'] as bool? ?? false;
|
||
final claimed = newUserBonus?['claimed'] as bool? ?? false;
|
||
final deposited = newUserBonus?['deposited'] as bool? ?? false;
|
||
|
||
// 状态判定
|
||
String badgeText;
|
||
bool showAvailableBadge;
|
||
String buttonText;
|
||
bool canClaim;
|
||
String description;
|
||
|
||
if (claimed) {
|
||
badgeText = '已领取';
|
||
showAvailableBadge = false;
|
||
buttonText = '已领取';
|
||
canClaim = false;
|
||
description = '新人福利已领取';
|
||
} else if (eligible) {
|
||
badgeText = '可领取';
|
||
showAvailableBadge = true;
|
||
buttonText = '立即领取';
|
||
canClaim = true;
|
||
description = '完成首次充值即可领取';
|
||
} else {
|
||
badgeText = deposited ? '已充值' : '待解锁';
|
||
showAvailableBadge = false;
|
||
buttonText = '未解锁';
|
||
canClaim = false;
|
||
description = deposited ? '已充值,等待系统确认' : '完成首次充值即可领取';
|
||
}
|
||
|
||
return Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: _cardDecoration(),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Header: 标题 + 状态标签
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
'新人福利',
|
||
style: AppTextStyles.headlineLarge(context).copyWith(
|
||
fontWeight: FontWeight.w700,
|
||
),
|
||
),
|
||
if (showAvailableBadge)
|
||
_statusBadge(badgeText, profitGreen, profitGreenBg),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
'+100 USDT',
|
||
style: AppTextStyles.displayLarge(context).copyWith(
|
||
fontWeight: FontWeight.w800,
|
||
color: claimed ? context.colors.onSurfaceVariant : profitGreen,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
description,
|
||
style: AppTextStyles.bodyLarge(context).copyWith(
|
||
color: context.colors.onSurfaceVariant,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
_fullWidthButton(
|
||
text: buttonText,
|
||
backgroundColor: profitGreen,
|
||
foregroundColor: context.colors.onPrimary,
|
||
onPressed: canClaim ? () => _claimNewUserBonus() : null,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================
|
||
// 推广奖励列表
|
||
// ============================================
|
||
|
||
Widget _buildReferralRewardsSection(BuildContext context) {
|
||
final referralRewards =
|
||
_welfareData?['referralRewards'] as List<dynamic>? ?? [];
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Section Header
|
||
Text(
|
||
'推广奖励',
|
||
style: AppTextStyles.headlineLarge(context),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'每邀请一位好友充值达标,奖励100 USDT',
|
||
style: AppTextStyles.bodySmall(context).copyWith(
|
||
color: context.appColors.onSurfaceMuted,
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// 推广列表卡片
|
||
Container(
|
||
width: double.infinity,
|
||
decoration: _cardDecoration(),
|
||
child: referralRewards.isEmpty
|
||
? _buildEmptyReferralList(context)
|
||
: _buildReferralListItems(context, referralRewards),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildEmptyReferralList(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.all(32),
|
||
child: Center(
|
||
child: Column(
|
||
children: [
|
||
Icon(
|
||
LucideIcons.users,
|
||
size: 36,
|
||
color: context.appColors.onSurfaceMuted.withValues(alpha: 0.4),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'暂无推广用户',
|
||
style: AppTextStyles.bodyLarge(context).copyWith(
|
||
color: context.colors.onSurfaceVariant,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildReferralListItems(BuildContext context, List<dynamic> referralRewards) {
|
||
return Column(
|
||
children: List.generate(referralRewards.length, (index) {
|
||
final data = referralRewards[index] as Map<String, dynamic>;
|
||
final username = data['username'] as String? ?? '';
|
||
final totalDeposit = data['totalDeposit']?.toString() ?? '0';
|
||
final claimableCount = data['claimableCount'] as int? ?? 0;
|
||
final milestones = data['milestones'] as List<dynamic>? ?? [];
|
||
final isLast = index == referralRewards.length - 1;
|
||
|
||
// 进度计算
|
||
final progress = _computeProgress(milestones, totalDeposit);
|
||
// 操作按钮
|
||
final actionWidget = _buildReferralAction(
|
||
data: data,
|
||
claimableCount: claimableCount,
|
||
milestones: milestones,
|
||
progress: progress,
|
||
);
|
||
// 进度条颜色
|
||
final progressColor = _referralProgressColor(claimableCount, progress);
|
||
|
||
return Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
_buildAvatar(username),
|
||
const SizedBox(width: 10),
|
||
Text(
|
||
username,
|
||
style: AppTextStyles.headlineSmall(context),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Text(
|
||
'充值: \u00A5$totalDeposit',
|
||
style: AppTextStyles.bodySmall(context),
|
||
),
|
||
],
|
||
),
|
||
if (actionWidget != null) actionWidget,
|
||
],
|
||
),
|
||
const SizedBox(height: 10),
|
||
ClipRRect(
|
||
borderRadius: BorderRadius.circular(3),
|
||
child: SizedBox(
|
||
height: 6,
|
||
child: LinearProgressIndicator(
|
||
value: progress,
|
||
backgroundColor: context.appColors.surfaceCardHigh,
|
||
valueColor: AlwaysStoppedAnimation<Color>(progressColor),
|
||
minHeight: 6,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (!isLast)
|
||
Divider(
|
||
height: 1,
|
||
thickness: 1,
|
||
color: context.appColors.ghostBorder,
|
||
),
|
||
],
|
||
);
|
||
}),
|
||
);
|
||
}
|
||
|
||
Widget _buildAvatar(String username) {
|
||
return Container(
|
||
width: 32,
|
||
height: 32,
|
||
decoration: BoxDecoration(
|
||
color: context.appColors.surfaceCardHigh,
|
||
shape: BoxShape.circle,
|
||
),
|
||
child: Center(
|
||
child: Text(
|
||
username.isNotEmpty ? username[0].toUpperCase() : '?',
|
||
style: AppTextStyles.headlineSmall(context).copyWith(
|
||
color: context.colors.onSurfaceVariant,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 计算推荐奖励进度
|
||
double _computeProgress(List<dynamic> milestones, String totalDeposit) {
|
||
if (milestones.isNotEmpty) {
|
||
int earnedCount = milestones.where((m) {
|
||
final milestone = m as Map<String, dynamic>;
|
||
return milestone['earned'] as bool? ?? false;
|
||
}).length;
|
||
return earnedCount / milestones.length;
|
||
}
|
||
final deposit = double.tryParse(totalDeposit) ?? 0;
|
||
return (deposit / 1000).clamp(0.0, 1.0);
|
||
}
|
||
|
||
/// 根据状态获取进度条颜色
|
||
Color _referralProgressColor(int claimableCount, double progress) {
|
||
if (claimableCount > 0) return context.appColors.up;
|
||
if (progress > 0) return context.appColors.accentPrimary;
|
||
return context.appColors.surfaceCardHigh;
|
||
}
|
||
|
||
/// 构建推荐奖励的操作按钮
|
||
Widget? _buildReferralAction({
|
||
required Map<String, dynamic> data,
|
||
required int claimableCount,
|
||
required List<dynamic> milestones,
|
||
required double progress,
|
||
}) {
|
||
final profitGreen = context.appColors.up;
|
||
final profitGreenBg = context.appColors.upBackground;
|
||
|
||
if (claimableCount > 0) {
|
||
final int milestoneValue = milestones.isNotEmpty
|
||
? (milestones.firstWhere(
|
||
(m) => (m as Map<String, dynamic>)['claimable'] == true,
|
||
orElse: () => milestones.first,
|
||
) as Map<String, dynamic>)['milestone'] as int? ?? 1
|
||
: 1;
|
||
|
||
return GestureDetector(
|
||
onTap: () => _claimReferralBonus(data['userId'] as int, milestoneValue),
|
||
child: _statusBadge('领取', profitGreen, profitGreenBg),
|
||
);
|
||
}
|
||
if (progress > 0) {
|
||
final warningColor = AppColorScheme.warning;
|
||
return _statusBadge('进行中', warningColor, warningColor.withValues(alpha: 0.15));
|
||
}
|
||
return Text(
|
||
'待达标',
|
||
style: AppTextStyles.labelLarge(context).copyWith(color: context.appColors.onSurfaceMuted),
|
||
);
|
||
}
|
||
|
||
// ============================================
|
||
// 奖励规则卡片
|
||
// ============================================
|
||
|
||
Widget _buildRulesCard(BuildContext context) {
|
||
return Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
||
decoration: BoxDecoration(
|
||
color: context.appColors.surfaceCardHigh,
|
||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'奖励规则',
|
||
style: AppTextStyles.headlineSmall(context),
|
||
),
|
||
const SizedBox(height: 8),
|
||
_buildRuleItem('新用户注册完成实名认证奖励 100 USDT'),
|
||
_buildRuleItem('邀请好友充值每达 1000 USDT,双方各获得 100 USDT'),
|
||
_buildRuleItem('奖励直接发放至资金账户'),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildRuleItem(String text) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||
child: Text(
|
||
'\u2022 $text',
|
||
style: AppTextStyles.bodyMedium(context).copyWith(
|
||
color: context.colors.onSurfaceVariant,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================
|
||
// 业务逻辑
|
||
// ============================================
|
||
|
||
Future<void> _claimNewUserBonus() async {
|
||
try {
|
||
final bonusService = context.read<BonusService>();
|
||
final response = await bonusService.claimNewUserBonus();
|
||
if (!mounted) return;
|
||
|
||
if (response.success) {
|
||
context.read<AssetProvider>().refreshAll(force: true);
|
||
context.read<AppEventBus>().fire(AppEventType.assetChanged);
|
||
ToastUtils.show('领取成功!100 USDT 已到账');
|
||
_loadData();
|
||
} else {
|
||
ToastUtils.show(response.message ?? '领取失败');
|
||
}
|
||
} catch (e) {
|
||
ToastUtils.show('领取失败: $e');
|
||
}
|
||
}
|
||
|
||
Future<void> _claimReferralBonus(int referredUserId, int milestone) async {
|
||
try {
|
||
final bonusService = context.read<BonusService>();
|
||
final response = await bonusService.claimReferralBonus(
|
||
referredUserId,
|
||
milestone,
|
||
);
|
||
if (!mounted) return;
|
||
|
||
if (response.success) {
|
||
context.read<AssetProvider>().refreshAll(force: true);
|
||
context.read<AppEventBus>().fire(AppEventType.assetChanged);
|
||
ToastUtils.show('领取成功!100 USDT 已到账');
|
||
_loadData();
|
||
} else {
|
||
ToastUtils.show(response.message ?? '领取失败');
|
||
}
|
||
} catch (e) {
|
||
ToastUtils.show('领取失败: $e');
|
||
}
|
||
}
|
||
}
|