Files
monisuo/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart

644 lines
20 KiB
Dart
Raw Normal View History

2026-04-04 21:19:57 +08:00
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';
2026-04-04 21:19:57 +08:00
import '../../../core/utils/toast_utils.dart';
2026-04-05 19:43:31 +08:00
import '../../../core/event/app_event_bus.dart';
2026-04-04 21:19:57 +08:00
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,
),
),
),
);
}
2026-04-04 21:19:57 +08:00
@override
Widget build(BuildContext context) {
final goldAccent = context.appColors.accentPrimary;
2026-04-04 21:19:57 +08:00
return Scaffold(
backgroundColor: context.colors.surface,
2026-04-04 21:19:57 +08:00
appBar: AppBar(
backgroundColor: context.colors.surface,
2026-04-04 21:19:57 +08:00
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
titleSpacing: 0,
2026-04-04 21:19:57 +08:00
title: Text(
'福利中心',
style: AppTextStyles.headlineLarge(context),
2026-04-04 21:19:57 +08:00
),
leading: IconButton(
icon: Icon(LucideIcons.arrowLeft, color: context.colors.onSurface, size: 24),
2026-04-04 21:19:57 +08:00
onPressed: () => Navigator.of(context).pop(),
),
),
body: _isLoading
? Center(
child: CircularProgressIndicator(
color: goldAccent,
strokeWidth: 2.5,
),
2026-04-04 21:19:57 +08:00
)
: RefreshIndicator(
onRefresh: _loadData,
color: goldAccent,
2026-04-04 21:19:57 +08:00
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
2026-04-04 21:19:57 +08:00
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),
2026-04-04 21:19:57 +08:00
],
),
),
),
);
}
// ============================================
// 推广码卡片(金色渐变边框 Hero Card
// ============================================
Widget _buildReferralCodeCard(BuildContext context) {
final goldAccent = context.appColors.accentPrimary;
2026-04-04 21:19:57 +08:00
final referralCode = _welfareData?['referralCode'] as String? ?? '';
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: _goldGradientDecoration(),
2026-04-04 21:19:57 +08:00
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row: gift icon + 标题
2026-04-04 21:19:57 +08:00
Row(
children: [
Icon(LucideIcons.gift, color: goldAccent, size: 24),
const SizedBox(width: 10),
2026-04-04 21:19:57 +08:00
Text(
'我的邀请码',
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
2026-04-04 21:19:57 +08:00
),
),
],
),
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
: () {
2026-04-04 21:19:57 +08:00
Clipboard.setData(ClipboardData(text: referralCode));
ToastUtils.show('邀请码已复制');
2026-04-04 21:19:57 +08:00
},
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),
),
2026-04-04 21:19:57 +08:00
),
),
],
),
);
}
// ============================================
// 新人福利卡片
// ============================================
Widget _buildNewUserBonusCard(BuildContext context) {
final profitGreen = context.appColors.up;
final profitGreenBg = context.appColors.upBackground;
2026-04-04 21:19:57 +08:00
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;
2026-04-04 21:19:57 +08:00
String buttonText;
bool canClaim;
String description;
2026-04-04 21:19:57 +08:00
if (claimed) {
badgeText = '已领取';
showAvailableBadge = false;
2026-04-04 21:19:57 +08:00
buttonText = '已领取';
canClaim = false;
description = '新人福利已领取';
2026-04-04 21:19:57 +08:00
} else if (eligible) {
badgeText = '可领取';
showAvailableBadge = true;
buttonText = '立即领取';
2026-04-04 21:19:57 +08:00
canClaim = true;
description = '完成首次充值即可领取';
2026-04-04 21:19:57 +08:00
} else {
badgeText = deposited ? '已充值' : '待解锁';
showAvailableBadge = false;
2026-04-04 21:19:57 +08:00
buttonText = '未解锁';
canClaim = false;
description = deposited ? '已充值,等待系统确认' : '完成首次充值即可领取';
2026-04-04 21:19:57 +08:00
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: _cardDecoration(),
2026-04-04 21:19:57 +08:00
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header: 标题 + 状态标签
2026-04-04 21:19:57 +08:00
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
2026-04-04 21:19:57 +08:00
children: [
Text(
'新人福利',
style: AppTextStyles.headlineLarge(context).copyWith(
fontWeight: FontWeight.w700,
2026-04-04 21:19:57 +08:00
),
),
if (showAvailableBadge)
_statusBadge(badgeText, profitGreen, profitGreenBg),
2026-04-04 21:19:57 +08:00
],
),
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),
2026-04-04 21:19:57 +08:00
Text(
description,
style: AppTextStyles.bodyLarge(context).copyWith(
color: context.colors.onSurfaceVariant,
2026-04-04 21:19:57 +08:00
),
),
const SizedBox(height: 16),
_fullWidthButton(
text: buttonText,
backgroundColor: profitGreen,
foregroundColor: context.colors.onPrimary,
onPressed: canClaim ? () => _claimNewUserBonus() : null,
),
2026-04-04 21:19:57 +08:00
],
),
);
}
// ============================================
// 推广奖励列表
// ============================================
Widget _buildReferralRewardsSection(BuildContext context) {
2026-04-04 21:19:57 +08:00
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,
),
2026-04-04 21:19:57 +08:00
),
const SizedBox(height: 12),
// 推广列表卡片
Container(
width: double.infinity,
decoration: _cardDecoration(),
child: referralRewards.isEmpty
? _buildEmptyReferralList(context)
: _buildReferralListItems(context, referralRewards),
2026-04-04 21:19:57 +08:00
),
],
);
}
Widget _buildEmptyReferralList(BuildContext context) {
2026-04-04 21:19:57 +08:00
return Padding(
padding: const EdgeInsets.all(32),
child: Center(
2026-04-04 21:19:57 +08:00
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),
2026-04-04 21:19:57 +08:00
),
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,
),
2026-04-04 21:19:57 +08:00
),
),
],
),
2026-04-04 21:19:57 +08:00
),
if (!isLast)
Divider(
height: 1,
thickness: 1,
color: context.appColors.ghostBorder,
),
2026-04-04 21:19:57 +08:00
],
);
}),
2026-04-04 21:19:57 +08:00
);
}
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) {
2026-04-04 21:19:57 +08:00
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
2026-04-04 21:19:57 +08:00
decoration: BoxDecoration(
color: context.appColors.surfaceCardHigh,
2026-04-04 21:19:57 +08:00
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'奖励规则',
style: AppTextStyles.headlineSmall(context),
2026-04-04 21:19:57 +08:00
),
const SizedBox(height: 8),
_buildRuleItem('新用户注册完成实名认证奖励 100 USDT'),
_buildRuleItem('邀请好友充值每达 1000 USDT双方各获得 100 USDT'),
_buildRuleItem('奖励直接发放至资金账户'),
2026-04-04 21:19:57 +08:00
],
),
);
}
Widget _buildRuleItem(String text) {
2026-04-04 21:19:57 +08:00
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Text(
'\u2022 $text',
style: AppTextStyles.bodyMedium(context).copyWith(
color: context.colors.onSurfaceVariant,
),
2026-04-04 21:19:57 +08:00
),
);
}
// ============================================
// 业务逻辑
// ============================================
2026-04-04 21:19:57 +08:00
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);
2026-04-05 19:43:31 +08:00
context.read<AppEventBus>().fire(AppEventType.assetChanged);
2026-04-04 21:19:57 +08:00
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);
2026-04-05 19:43:31 +08:00
context.read<AppEventBus>().fire(AppEventType.assetChanged);
2026-04-04 21:19:57 +08:00
ToastUtils.show('领取成功100 USDT 已到账');
_loadData();
} else {
ToastUtils.show(response.message ?? '领取失败');
}
} catch (e) {
ToastUtils.show('领取失败: $e');
}
}
}