Files
monisuo/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart
sion fabce57657 fix(ui): 修复多个UI样式问题
1. 修复 Light Mode 卡片背景色(surfaceCard: #FFFFFF)
2. 修复首页充值按钮颜色(使用主色实心背景+白色文字)
3. 修复'更多'和'资产详情'文字颜色(使用 text-muted)
4. 修复邀请码卡片背景色(使用琥珀色渐变)
5. 修复'复制邀请码'按钮文字颜色(使用 onPrimary)
2026-04-06 03:40:45 +08:00

644 lines
20 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.
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');
}
}
}