Files
monisuo/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart
sion123 d8cd38c4de feat(theme): update color scheme with new Slate theme and improved surface hierarchy
Updated the app's color scheme to implement a new "Slate" theme with refined dark and light variants. Changed background colors from #0A0E14 to #0B1120 for dark mode and updated surface layer colors to follow Material Design 3 specifications. Modified text colors and outline variants for better contrast and accessibility. Updated font sizes in transaction details screen from 11px to 12px for improved readability.
2026-04-05 22:24:04 +08:00

757 lines
25 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 'package:google_fonts/google_fonts.dart';
import '../../../core/theme/app_color_scheme.dart';
import '../../../core/theme/app_spacing.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';
import '../../../providers/auth_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);
}
}
// ============================================
// 主题感知颜色辅助
// ============================================
/// 金色强调色 ($gold-accent)
Color _goldAccent(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? AppColorScheme.darkSecondary : const Color(0xFFF59E0B);
}
/// 金色强调色带透明度
Color _goldAccentWithOpacity(BuildContext context, double opacity) {
return _goldAccent(context).withOpacity(opacity);
}
/// 盈利绿色 ($profit-green)
Color _profitGreen(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? const Color(0xFF4ADE80) : const Color(0xFF16A34A);
}
/// 盈利绿色背景 ($profit-green-bg)
Color _profitGreenBg(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? const Color(0xFF052E16) : const Color(0xFFF0FDF4);
}
/// 文字静默色 ($text-muted)
Color _textMuted(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? const Color(0xFF64748B) : const Color(0xFF94A3B8);
}
/// 第三级背景色 ($bg-tertiary)
Color _bgTertiary(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? AppColorScheme.darkSurfaceContainerHigh : const Color(0xFFF1F5F9);
}
/// 卡片表面色 ($surface-card)
Color _surfaceCard(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? AppColorScheme.darkSurfaceContainer : Colors.white;
}
/// 反色文字 ($text-inverse)
Color _textInverse(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return isDark ? const Color(0xFF0F172A) : Colors.white;
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDark
? AppColorScheme.darkBackground
: const Color(0xFFF8FAFC),
appBar: AppBar(
backgroundColor: isDark
? AppColorScheme.darkBackground
: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
titleSpacing: 0,
title: Text(
'福利中心',
style: GoogleFonts.inter(
fontSize: 17,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
leading: IconButton(
icon: Icon(LucideIcons.arrowLeft, color: colorScheme.onSurface, size: 24),
onPressed: () => Navigator.of(context).pop(),
),
),
body: _isLoading
? Center(
child: CircularProgressIndicator(
color: _goldAccent(context),
strokeWidth: 2.5,
),
)
: RefreshIndicator(
onRefresh: _loadData,
color: _goldAccent(context),
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 colorScheme = Theme.of(context).colorScheme;
final referralCode = _welfareData?['referralCode'] as String? ?? '';
final gold = _goldAccent(context);
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppRadius.xl),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
_goldAccentWithOpacity(context, 0.15),
_surfaceCard(context),
],
),
border: Border.all(
color: _goldAccentWithOpacity(context, 0.3),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row: gift icon + 标题
Row(
children: [
Icon(LucideIcons.gift, color: gold, size: 24),
const SizedBox(width: 10),
Text(
'我的邀请码',
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w700,
color: colorScheme.onSurface,
),
),
],
),
const SizedBox(height: 16),
// 邀请码
Text(
referralCode.isEmpty ? '暂无邀请码' : referralCode,
style: GoogleFonts.inter(
fontSize: 24,
fontWeight: FontWeight.w800,
letterSpacing: 2,
color: gold,
),
),
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: gold,
foregroundColor: _textInverse(context),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.lg),
),
disabledBackgroundColor: gold.withOpacity(0.4),
),
child: Text(
'复制邀请码',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
);
}
// ============================================
// 新人福利卡片
// ============================================
Widget _buildNewUserBonusCard(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
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;
final green = _profitGreen(context);
final greenBg = _profitGreenBg(context);
// 状态标签
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: BoxDecoration(
color: _surfaceCard(context),
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(
color: colorScheme.outlineVariant.withOpacity(0.15),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header: 标题 + 状态标签
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'新人福利',
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w700,
color: colorScheme.onSurface,
),
),
if (showAvailableBadge)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: greenBg,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
badgeText,
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w600,
color: green,
),
),
),
],
),
const SizedBox(height: 12),
// 金额
Text(
'+100 USDT',
style: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w800,
color: claimed ? colorScheme.onSurfaceVariant : green,
),
),
const SizedBox(height: 8),
// 描述
Text(
description,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.normal,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
// 领取按钮
SizedBox(
width: double.infinity,
height: 44,
child: ElevatedButton(
onPressed: canClaim ? () => _claimNewUserBonus() : null,
style: ElevatedButton.styleFrom(
backgroundColor: green,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.lg),
),
disabledBackgroundColor: green.withOpacity(0.3),
disabledForegroundColor: Colors.white70,
),
child: Text(
buttonText,
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
);
}
// ============================================
// 推广奖励列表
// ============================================
Widget _buildReferralRewardsSection(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final referralRewards =
_welfareData?['referralRewards'] as List<dynamic>? ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section Header
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'推广奖励',
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
'每邀请一位好友充值达标奖励100 USDT',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.normal,
color: _textMuted(context),
),
),
],
),
const SizedBox(height: 12),
// 推广列表卡片
Container(
width: double.infinity,
decoration: BoxDecoration(
color: _surfaceCard(context),
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(
color: colorScheme.outlineVariant.withOpacity(0.15),
),
),
child: referralRewards.isEmpty
? _buildEmptyReferralList(context)
: _buildReferralListItems(context, referralRewards),
),
],
);
}
Widget _buildEmptyReferralList(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.all(32),
child: Center(
child: Column(
children: [
Icon(
LucideIcons.users,
size: 36,
color: _textMuted(context).withOpacity(0.4),
),
const SizedBox(height: 8),
Text(
'暂无推广用户',
style: GoogleFonts.inter(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
);
}
Widget _buildReferralListItems(BuildContext context, List<dynamic> referralRewards) {
final colorScheme = Theme.of(context).colorScheme;
final gold = _goldAccent(context);
final green = _profitGreen(context);
final greenBg = _profitGreenBg(context);
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;
// 判断状态
bool hasClaimable = claimableCount > 0;
bool hasAnyMilestone = milestones.isNotEmpty;
bool allClaimed = milestones.isNotEmpty &&
milestones.every((m) => (m as Map<String, dynamic>)['claimed'] == true);
// 进度计算
double progress = 0;
if (milestones.isNotEmpty) {
int earnedCount = milestones.where((m) {
final milestone = m as Map<String, dynamic>;
return milestone['earned'] as bool? ?? false;
}).length;
progress = earnedCount / milestones.length;
} else {
// 无里程碑数据时,根据充值金额估算
final deposit = double.tryParse(totalDeposit) ?? 0;
progress = (deposit / 1000).clamp(0.0, 1.0);
}
// 状态颜色和文字
Color progressColor;
Color statusTextColor;
String statusText;
Widget? actionWidget;
if (hasClaimable) {
// 可领取 (achieved, green)
progressColor = green;
statusTextColor = green;
statusText = '';
actionWidget = GestureDetector(
onTap: () => _claimReferralBonus(data['userId'] as int, milestones.isNotEmpty ? (milestones.firstWhere(
(m) => (m as Map<String, dynamic>)['claimable'] == true,
orElse: () => milestones.first,
) as Map<String, dynamic>)['milestone'] as int? ?? 1 : 1),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: greenBg,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'领取',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: green,
),
),
),
);
} else if (progress > 0) {
// 进行中 (amber / gold)
progressColor = gold;
statusTextColor = const Color(0xFFD97706);
statusText = '';
actionWidget = Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFFFEF3C7),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'进行中',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
color: statusTextColor,
),
),
);
} else {
// 待达标 (gray)
progressColor = _bgTertiary(context);
statusTextColor = _textMuted(context);
statusText = '';
actionWidget = Text(
'待达标',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _textMuted(context),
),
);
}
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Top Row: avatar + name + deposit + action
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// Avatar
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: _bgTertiary(context),
shape: BoxShape.circle,
),
child: Center(
child: Text(
username.isNotEmpty ? username[0].toUpperCase() : '?',
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
color: colorScheme.onSurfaceVariant,
),
),
),
),
const SizedBox(width: 10),
Text(
username,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
),
const SizedBox(width: 10),
Text(
'充值: \u00A5$totalDeposit',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.normal,
color: colorScheme.onSurfaceVariant,
),
),
],
),
if (actionWidget != null) actionWidget,
],
),
const SizedBox(height: 10),
// Progress bar
ClipRRect(
borderRadius: BorderRadius.circular(3),
child: SizedBox(
height: 6,
child: LinearProgressIndicator(
value: progress,
backgroundColor: _bgTertiary(context),
valueColor: AlwaysStoppedAnimation<Color>(progressColor),
minHeight: 6,
),
),
),
],
),
),
if (!isLast)
Divider(
height: 1,
thickness: 1,
color: colorScheme.outlineVariant.withOpacity(0.15),
),
],
);
}),
);
}
// ============================================
// 奖励规则卡片
// ============================================
Widget _buildRulesCard(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
decoration: BoxDecoration(
color: _bgTertiary(context),
borderRadius: BorderRadius.circular(AppRadius.lg),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'奖励规则',
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 8),
_buildRuleItem('新用户注册完成实名认证奖励 100 USDT', context),
_buildRuleItem('邀请好友充值每达 1000 USDT双方各获得 100 USDT', context),
_buildRuleItem('奖励直接发放至资金账户', context),
],
),
);
}
Widget _buildRuleItem(String text, BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Text(
'\u2022 $text',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).brightness == Brightness.dark
? AppColorScheme.darkOnSurfaceVariant
: const Color(0xFF475569),
),
),
);
}
// ============================================
// 业务逻辑
// ============================================
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');
}
}
}