2026-04-04 21:19:57 +08:00
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/services.dart';
|
2026-04-08 12:24:24 +08:00
|
|
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
import '../../../core/theme/app_spacing.dart';
|
2026-04-05 23:37:27 +08:00
|
|
|
|
import '../../../core/theme/app_theme.dart';
|
2026-04-06 01:58:08 +08:00
|
|
|
|
import '../../../core/theme/app_theme_extension.dart';
|
2026-04-06 03:21:44 +08:00
|
|
|
|
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';
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 福利中心頁面
|
2026-04-04 21:19:57 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:38:56 +08:00
|
|
|
|
// ============================================
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 容器樣式輔助
|
2026-04-05 22:38:56 +08:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 標準卡片容器
|
2026-04-05 22:38:56 +08:00
|
|
|
|
BoxDecoration _cardDecoration({Color? borderColor}) {
|
|
|
|
|
|
return BoxDecoration(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: context.appColors.surfaceCard,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.lg),
|
|
|
|
|
|
border: Border.all(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: borderColor ?? context.appColors.ghostBorder,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 金色漸變卡片容器
|
2026-04-05 22:38:56 +08:00
|
|
|
|
BoxDecoration _goldGradientDecoration() {
|
2026-04-06 03:40:45 +08:00
|
|
|
|
final isDark = context.isDark;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// Light Mode: 琥珀色漸變(從 amber 15% 到深灰 5%)
|
|
|
|
|
|
// Dark Mode: 金色漸變
|
2026-04-06 03:40:45 +08:00
|
|
|
|
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
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-04-05 22:38:56 +08:00
|
|
|
|
return BoxDecoration(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.xl),
|
|
|
|
|
|
gradient: LinearGradient(
|
|
|
|
|
|
begin: Alignment.topCenter,
|
|
|
|
|
|
end: Alignment.bottomCenter,
|
2026-04-06 03:40:45 +08:00
|
|
|
|
colors: gradientColors,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
|
|
|
|
|
border: Border.all(
|
2026-04-06 03:40:45 +08:00
|
|
|
|
color: isDark
|
|
|
|
|
|
? context.appColors.accentPrimary.withValues(alpha: 0.3)
|
|
|
|
|
|
: const Color(0xFFF59E0B).withValues(alpha: 0.3),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
width: 1,
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 狀態膠囊標籤
|
2026-04-05 22:38:56 +08:00
|
|
|
|
Widget _statusBadge(String text, Color textColor, Color bgColor) {
|
|
|
|
|
|
return Container(
|
2026-04-06 03:57:00 +08:00
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), // [4,10] → [6,14]
|
2026-04-05 22:38:56 +08:00
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: bgColor,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.sm),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
text,
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.labelSmall(context).copyWith(color: textColor),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 全寬按鈕
|
2026-04-05 22:38:56 +08:00
|
|
|
|
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,
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineMedium(context).copyWith(
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
|
|
|
|
|
color: foregroundColor,
|
|
|
|
|
|
),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
2026-04-05 22:24:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 21:19:57 +08:00
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-04-06 01:58:08 +08:00
|
|
|
|
final goldAccent = context.appColors.accentPrimary;
|
2026-04-04 21:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
return Scaffold(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
appBar: AppBar(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
elevation: 0,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
scrolledUnderElevation: 0,
|
|
|
|
|
|
centerTitle: false,
|
|
|
|
|
|
titleSpacing: 0,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
title: Text(
|
|
|
|
|
|
'福利中心',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineLarge(context),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
leading: IconButton(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
icon: Icon(LucideIcons.arrowLeft, color: Theme.of(context).colorScheme.onSurface, size: 24),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
body: _isLoading
|
|
|
|
|
|
? Center(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
child: CircularProgressIndicator(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: goldAccent,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
strokeWidth: 2.5,
|
|
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
)
|
|
|
|
|
|
: RefreshIndicator(
|
|
|
|
|
|
onRefresh: _loadData,
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: goldAccent,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-04-05 22:24:04 +08:00
|
|
|
|
_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
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 推廣碼卡片(金色漸變邊框 Hero Card)
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildReferralCodeCard(BuildContext context) {
|
2026-04-06 01:58:08 +08:00
|
|
|
|
final goldAccent = context.appColors.accentPrimary;
|
2026-04-04 21:19:57 +08:00
|
|
|
|
final referralCode = _welfareData?['referralCode'] as String? ?? '';
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
return Container(
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
padding: const EdgeInsets.all(24),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
decoration: _goldGradientDecoration(),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// Header Row: gift icon + 標題
|
2026-04-04 21:19:57 +08:00
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
2026-04-06 01:58:08 +08:00
|
|
|
|
Icon(LucideIcons.gift, color: goldAccent, size: 24),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(width: 10),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'我的邀請碼',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineLarge(context).copyWith(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
fontWeight: FontWeight.w700,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
|
|
Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
referralCode.isEmpty ? '暫無邀請碼' : referralCode,
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.displayMedium(context).copyWith(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
fontSize: 24, // 明確設置為 24px
|
2026-04-05 22:24:04 +08:00
|
|
|
|
fontWeight: FontWeight.w800,
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: goldAccent,
|
2026-04-05 23:37:27 +08:00
|
|
|
|
letterSpacing: 2,
|
|
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
|
|
|
|
|
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));
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showSuccess('邀請碼已複製');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
},
|
2026-04-05 22:24:04 +08:00
|
|
|
|
style: ElevatedButton.styleFrom(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
backgroundColor: goldAccent,
|
2026-04-08 12:24:24 +08:00
|
|
|
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
elevation: 0,
|
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.lg),
|
|
|
|
|
|
),
|
2026-04-06 01:58:08 +08:00
|
|
|
|
disabledBackgroundColor: goldAccent.withValues(alpha: 0.4),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
|
|
|
|
|
child: Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'複製邀請碼',
|
2026-04-08 12:24:24 +08:00
|
|
|
|
style: AppTextStyles.headlineMedium(context).copyWith(color: Theme.of(context).colorScheme.onPrimary),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// 新人福利卡片
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildNewUserBonusCard(BuildContext context) {
|
2026-04-06 01:58:08 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 狀態判定
|
2026-04-05 22:24:04 +08:00
|
|
|
|
String badgeText;
|
|
|
|
|
|
bool showAvailableBadge;
|
2026-04-04 21:19:57 +08:00
|
|
|
|
String buttonText;
|
|
|
|
|
|
bool canClaim;
|
2026-04-05 22:24:04 +08:00
|
|
|
|
String description;
|
2026-04-04 21:19:57 +08:00
|
|
|
|
|
|
|
|
|
|
if (claimed) {
|
2026-04-07 01:05:05 +08:00
|
|
|
|
badgeText = '已領取';
|
2026-04-05 22:24:04 +08:00
|
|
|
|
showAvailableBadge = false;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
buttonText = '已領取';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
canClaim = false;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
description = '新人福利已領取';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
} else if (eligible) {
|
2026-04-07 01:05:05 +08:00
|
|
|
|
badgeText = '可領取';
|
2026-04-05 22:24:04 +08:00
|
|
|
|
showAvailableBadge = true;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
buttonText = '立即領取';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
canClaim = true;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
description = '完成首次充值即可領取';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
} else {
|
2026-04-07 01:05:05 +08:00
|
|
|
|
badgeText = deposited ? '已充值' : '待解鎖';
|
2026-04-05 22:24:04 +08:00
|
|
|
|
showAvailableBadge = false;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
buttonText = '未解鎖';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
canClaim = false;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
description = deposited ? '已充值,等待系統確認' : '完成首次充值即可領取';
|
2026-04-04 21:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
return Container(
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
padding: const EdgeInsets.all(20),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
decoration: _cardDecoration(),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// Header: 標題 + 狀態標籤
|
2026-04-04 21:19:57 +08:00
|
|
|
|
Row(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
children: [
|
2026-04-05 22:24:04 +08:00
|
|
|
|
Text(
|
|
|
|
|
|
'新人福利',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineLarge(context).copyWith(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
fontWeight: FontWeight.w700,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
if (showAvailableBadge)
|
2026-04-06 01:58:08 +08:00
|
|
|
|
_statusBadge(badgeText, profitGreen, profitGreenBg),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'+100 USDT',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.displayLarge(context).copyWith(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
fontWeight: FontWeight.w800,
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: claimed ? Theme.of(context).colorScheme.onSurfaceVariant : profitGreen,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 8),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
Text(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
description,
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.bodyLarge(context).copyWith(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(height: 16),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
_fullWidthButton(
|
|
|
|
|
|
text: buttonText,
|
2026-04-06 01:58:08 +08:00
|
|
|
|
backgroundColor: profitGreen,
|
2026-04-08 12:24:24 +08:00
|
|
|
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
onPressed: canClaim ? () => _claimNewUserBonus() : null,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 推廣獎勵列表
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildReferralRewardsSection(BuildContext context) {
|
2026-04-04 21:19:57 +08:00
|
|
|
|
final referralRewards =
|
|
|
|
|
|
_welfareData?['referralRewards'] as List<dynamic>? ?? [];
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 彙總統計
|
2026-04-06 11:31:28 +08:00
|
|
|
|
int totalEarned = 0;
|
|
|
|
|
|
int totalClaimable = 0;
|
|
|
|
|
|
for (var r in referralRewards) {
|
|
|
|
|
|
final map = r as Map<String, dynamic>;
|
|
|
|
|
|
final milestones = map['milestones'] as List<dynamic>? ?? [];
|
|
|
|
|
|
for (var m in milestones) {
|
|
|
|
|
|
final ms = m as Map<String, dynamic>;
|
|
|
|
|
|
if (ms['claimed'] as bool? ?? false) totalEarned++;
|
|
|
|
|
|
if (ms['claimable'] as bool? ?? false) totalClaimable++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 21:19:57 +08:00
|
|
|
|
return Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// Section Header
|
2026-04-05 22:38:56 +08:00
|
|
|
|
Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'推廣獎勵',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineLarge(context),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 4),
|
|
|
|
|
|
Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'每邀請一位好友充值達標,獎勵100 USDT',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.bodySmall(context).copyWith(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: context.appColors.onSurfaceMuted,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 彙總統計
|
2026-04-06 11:31:28 +08:00
|
|
|
|
if (referralRewards.isNotEmpty) ...[
|
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
|
Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: context.appColors.surfaceCard,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
border: Border.all(color: context.appColors.ghostBorder),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
children: [
|
2026-04-07 01:05:05 +08:00
|
|
|
|
_buildStatItem('已邀請', '${referralRewards.length}人'),
|
2026-04-06 11:31:28 +08:00
|
|
|
|
Container(width: 1, height: 20, color: context.appColors.ghostBorder),
|
2026-04-07 01:05:05 +08:00
|
|
|
|
_buildStatItem('已獲得', '${totalEarned * 100} USDT'),
|
2026-04-06 11:31:28 +08:00
|
|
|
|
if (totalClaimable > 0) ...[
|
|
|
|
|
|
Container(width: 1, height: 20, color: context.appColors.ghostBorder),
|
2026-04-07 01:05:05 +08:00
|
|
|
|
_buildStatItem('待領取', '$totalClaimable個', highlight: true),
|
2026-04-06 11:31:28 +08:00
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 推廣列表卡片
|
2026-04-05 22:24:04 +08:00
|
|
|
|
Container(
|
|
|
|
|
|
width: double.infinity,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
decoration: _cardDecoration(),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
child: referralRewards.isEmpty
|
|
|
|
|
|
? _buildEmptyReferralList(context)
|
|
|
|
|
|
: _buildReferralListItems(context, referralRewards),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 11:31:28 +08:00
|
|
|
|
Widget _buildStatItem(String label, String value, {bool highlight = false}) {
|
|
|
|
|
|
return Expanded(
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Text(label, style: AppTextStyles.bodySmall(context).copyWith(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
2026-04-06 11:31:28 +08:00
|
|
|
|
)),
|
|
|
|
|
|
const SizedBox(height: 2),
|
|
|
|
|
|
Text(value, style: AppTextStyles.headlineSmall(context).copyWith(
|
|
|
|
|
|
fontWeight: FontWeight.w700,
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: highlight ? context.appColors.up : Theme.of(context).colorScheme.onSurface,
|
2026-04-06 11:31:28 +08:00
|
|
|
|
)),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
Widget _buildEmptyReferralList(BuildContext context) {
|
2026-04-04 21:19:57 +08:00
|
|
|
|
return Padding(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
padding: const EdgeInsets.all(32),
|
|
|
|
|
|
child: Center(
|
2026-04-04 21:19:57 +08:00
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
2026-04-05 22:24:04 +08:00
|
|
|
|
Icon(
|
|
|
|
|
|
LucideIcons.users,
|
|
|
|
|
|
size: 36,
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: context.appColors.onSurfaceMuted.withValues(alpha: 0.4),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
|
Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'暫無推廣用戶',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.bodyLarge(context).copyWith(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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>? ?? [];
|
2026-04-06 11:31:28 +08:00
|
|
|
|
final userId = data['userId'] as int? ?? 0;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
final indirectRefCount = data['indirectRefCount'] as int? ?? 0;
|
|
|
|
|
|
final indirectClaimableCount = data['indirectClaimableCount'] as int? ?? 0;
|
|
|
|
|
|
final indirectMilestones = data['indirectMilestones'] as List<dynamic>? ?? [];
|
2026-04-05 22:24:04 +08:00
|
|
|
|
final isLast = index == referralRewards.length - 1;
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 進度計算
|
2026-04-05 22:38:56 +08:00
|
|
|
|
final progress = _computeProgress(milestones, totalDeposit);
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 操作按鈕
|
2026-04-05 22:38:56 +08:00
|
|
|
|
final actionWidget = _buildReferralAction(
|
|
|
|
|
|
data: data,
|
|
|
|
|
|
claimableCount: claimableCount,
|
|
|
|
|
|
milestones: milestones,
|
|
|
|
|
|
progress: progress,
|
|
|
|
|
|
);
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 進度條顏色
|
2026-04-05 22:38:56 +08:00
|
|
|
|
final progressColor = _referralProgressColor(claimableCount, progress);
|
2026-04-05 22:24:04 +08:00
|
|
|
|
|
|
|
|
|
|
return Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Padding(
|
|
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
2026-04-05 22:38:56 +08:00
|
|
|
|
_buildAvatar(username),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(width: 10),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
username,
|
2026-04-06 03:57:00 +08:00
|
|
|
|
style: AppTextStyles.headlineSmall(context).copyWith(
|
2026-04-06 11:31:28 +08:00
|
|
|
|
fontWeight: FontWeight.w500,
|
2026-04-06 03:57:00 +08:00
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(width: 10),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'充值: \u00A5$totalDeposit',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.bodySmall(context),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
if (actionWidget != null) actionWidget,
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
|
|
ClipRRect(
|
|
|
|
|
|
borderRadius: BorderRadius.circular(3),
|
|
|
|
|
|
child: SizedBox(
|
|
|
|
|
|
height: 6,
|
|
|
|
|
|
child: LinearProgressIndicator(
|
|
|
|
|
|
value: progress,
|
2026-04-06 01:58:08 +08:00
|
|
|
|
backgroundColor: context.appColors.surfaceCardHigh,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
valueColor: AlwaysStoppedAnimation<Color>(progressColor),
|
|
|
|
|
|
minHeight: 6,
|
|
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 里程碑詳情
|
2026-04-06 11:31:28 +08:00
|
|
|
|
if (milestones.isNotEmpty) ...[
|
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
|
_buildMilestoneDetails(milestones, userId),
|
|
|
|
|
|
],
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 間接推廣獎勵
|
|
|
|
|
|
if (indirectRefCount > 0) ...[
|
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
|
_buildIndirectSection(
|
|
|
|
|
|
userId,
|
|
|
|
|
|
indirectRefCount,
|
|
|
|
|
|
indirectClaimableCount,
|
|
|
|
|
|
indirectMilestones,
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
2026-04-05 22:24:04 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
if (!isLast)
|
|
|
|
|
|
Divider(
|
|
|
|
|
|
height: 1,
|
|
|
|
|
|
thickness: 1,
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: context.appColors.ghostBorder,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
],
|
2026-04-05 22:24:04 +08:00
|
|
|
|
);
|
|
|
|
|
|
}),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 里程碑詳情行 — 顯示每個里程碑狀態
|
2026-04-06 11:31:28 +08:00
|
|
|
|
Widget _buildMilestoneDetails(List<dynamic> milestones, int userId) {
|
|
|
|
|
|
final upColor = context.appColors.up;
|
|
|
|
|
|
|
|
|
|
|
|
return Wrap(
|
|
|
|
|
|
spacing: 6,
|
|
|
|
|
|
runSpacing: 6,
|
|
|
|
|
|
children: milestones.map((m) {
|
|
|
|
|
|
final ms = m as Map<String, dynamic>;
|
|
|
|
|
|
final milestoneVal = ms['milestone'] as int? ?? 1;
|
|
|
|
|
|
final earned = ms['earned'] as bool? ?? false;
|
|
|
|
|
|
final claimed = ms['claimed'] as bool? ?? false;
|
|
|
|
|
|
final claimable = ms['claimable'] as bool? ?? false;
|
|
|
|
|
|
final threshold = ms['threshold']?.toString() ?? '${milestoneVal * 1000}';
|
|
|
|
|
|
|
|
|
|
|
|
Color bgColor;
|
|
|
|
|
|
Color textColor;
|
|
|
|
|
|
String label;
|
|
|
|
|
|
|
|
|
|
|
|
if (claimed) {
|
|
|
|
|
|
bgColor = upColor.withValues(alpha: 0.1);
|
|
|
|
|
|
textColor = upColor;
|
|
|
|
|
|
label = '${threshold}✓';
|
|
|
|
|
|
} else if (claimable) {
|
|
|
|
|
|
bgColor = context.appColors.accentPrimary.withValues(alpha: 0.15);
|
|
|
|
|
|
textColor = context.appColors.accentPrimary;
|
|
|
|
|
|
label = '${threshold}🎁';
|
|
|
|
|
|
} else if (earned) {
|
|
|
|
|
|
bgColor = upColor.withValues(alpha: 0.06);
|
|
|
|
|
|
textColor = upColor;
|
|
|
|
|
|
label = '${threshold}';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bgColor = context.appColors.surfaceCardHigh;
|
2026-04-08 12:24:24 +08:00
|
|
|
|
textColor = Theme.of(context).colorScheme.onSurfaceVariant;
|
2026-04-06 11:31:28 +08:00
|
|
|
|
label = '${threshold}';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
|
onTap: claimable ? () => _claimReferralBonus(userId, milestoneVal) : null,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: bgColor,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.sm),
|
|
|
|
|
|
border: claimable ? Border.all(color: textColor.withValues(alpha: 0.3)) : null,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
label,
|
|
|
|
|
|
style: AppTextStyles.bodySmall(context).copyWith(
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
color: textColor,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}).toList(),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:38:56 +08:00
|
|
|
|
Widget _buildAvatar(String username) {
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
width: 32,
|
|
|
|
|
|
height: 32,
|
|
|
|
|
|
decoration: BoxDecoration(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: context.appColors.surfaceCardHigh,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
shape: BoxShape.circle,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Center(
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
username.isNotEmpty ? username[0].toUpperCase() : '?',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineSmall(context).copyWith(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
2026-04-05 22:38:56 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 計算推薦獎勵進度
|
2026-04-05 22:38:56 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 根據狀態獲取進度條顏色
|
2026-04-05 22:38:56 +08:00
|
|
|
|
Color _referralProgressColor(int claimableCount, double progress) {
|
2026-04-06 01:58:08 +08:00
|
|
|
|
if (claimableCount > 0) return context.appColors.up;
|
|
|
|
|
|
if (progress > 0) return context.appColors.accentPrimary;
|
|
|
|
|
|
return context.appColors.surfaceCardHigh;
|
2026-04-05 22:38:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 01:05:05 +08:00
|
|
|
|
/// 構建推薦獎勵的操作按鈕
|
2026-04-05 22:38:56 +08:00
|
|
|
|
Widget? _buildReferralAction({
|
|
|
|
|
|
required Map<String, dynamic> data,
|
|
|
|
|
|
required int claimableCount,
|
|
|
|
|
|
required List<dynamic> milestones,
|
|
|
|
|
|
required double progress,
|
|
|
|
|
|
}) {
|
2026-04-06 01:58:08 +08:00
|
|
|
|
final profitGreen = context.appColors.up;
|
|
|
|
|
|
final profitGreenBg = context.appColors.upBackground;
|
|
|
|
|
|
|
2026-04-05 22:38:56 +08:00
|
|
|
|
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),
|
2026-04-07 01:05:05 +08:00
|
|
|
|
child: _statusBadge('領取', profitGreen, profitGreenBg),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (progress > 0) {
|
2026-04-06 03:21:44 +08:00
|
|
|
|
final warningColor = AppColorScheme.warning;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
return _statusBadge('進行中', warningColor, warningColor.withValues(alpha: 0.15));
|
2026-04-05 22:38:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
return Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'待達標',
|
2026-04-06 01:58:08 +08:00
|
|
|
|
style: AppTextStyles.labelLarge(context).copyWith(color: context.appColors.onSurfaceMuted),
|
2026-04-05 22:38:56 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 間接推廣獎勵區塊
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildIndirectSection(
|
|
|
|
|
|
int directReferralId,
|
|
|
|
|
|
int indirectRefCount,
|
|
|
|
|
|
int indirectClaimableCount,
|
|
|
|
|
|
List<dynamic> indirectMilestones,
|
|
|
|
|
|
) {
|
|
|
|
|
|
final goldAccent = context.appColors.accentPrimary;
|
|
|
|
|
|
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
padding: const EdgeInsets.all(12),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: context.appColors.surfaceCardHigh.withValues(alpha: 0.5),
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Row(
|
|
|
|
|
|
children: [
|
|
|
|
|
|
Icon(LucideIcons.users, size: 14, color: goldAccent),
|
|
|
|
|
|
const SizedBox(width: 6),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
'已推廣 $indirectRefCount 人',
|
|
|
|
|
|
style: AppTextStyles.bodyMedium(context).copyWith(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
2026-04-07 01:05:05 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
if (indirectClaimableCount > 0) ...[
|
|
|
|
|
|
const Spacer(),
|
|
|
|
|
|
_statusBadge(
|
|
|
|
|
|
'$indirectClaimableCount 個可領',
|
|
|
|
|
|
context.appColors.up,
|
|
|
|
|
|
context.appColors.upBackground,
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
// 間接里程碑標籤
|
|
|
|
|
|
if (indirectMilestones.isNotEmpty) ...[
|
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
|
_buildIndirectMilestoneTags(directReferralId, indirectMilestones),
|
|
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildIndirectMilestoneTags(int directReferralId, List<dynamic> milestones) {
|
|
|
|
|
|
final upColor = context.appColors.up;
|
|
|
|
|
|
|
|
|
|
|
|
return Wrap(
|
|
|
|
|
|
spacing: 6,
|
|
|
|
|
|
runSpacing: 6,
|
|
|
|
|
|
children: milestones.map((m) {
|
|
|
|
|
|
final ms = m as Map<String, dynamic>;
|
|
|
|
|
|
final milestoneVal = ms['milestone'] as int? ?? 1;
|
|
|
|
|
|
final earned = ms['earned'] as bool? ?? false;
|
|
|
|
|
|
final claimed = ms['claimed'] as bool? ?? false;
|
|
|
|
|
|
final claimable = ms['claimable'] as bool? ?? false;
|
|
|
|
|
|
final indirectReferredUserId = ms['indirectReferredUserId'] as int? ?? 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 不顯示未達標的里程碑
|
|
|
|
|
|
if (!earned && !claimed) return const SizedBox.shrink();
|
|
|
|
|
|
|
|
|
|
|
|
Color bgColor;
|
|
|
|
|
|
Color textColor;
|
|
|
|
|
|
String label;
|
|
|
|
|
|
|
|
|
|
|
|
if (claimed) {
|
|
|
|
|
|
bgColor = upColor.withValues(alpha: 0.1);
|
|
|
|
|
|
textColor = upColor;
|
|
|
|
|
|
label = '50\u2713';
|
|
|
|
|
|
} else if (claimable) {
|
|
|
|
|
|
bgColor = context.appColors.accentPrimary.withValues(alpha: 0.15);
|
|
|
|
|
|
textColor = context.appColors.accentPrimary;
|
|
|
|
|
|
label = '50\u{1F381}';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bgColor = context.appColors.surfaceCardHigh;
|
2026-04-08 12:24:24 +08:00
|
|
|
|
textColor = Theme.of(context).colorScheme.onSurfaceVariant;
|
2026-04-07 01:05:05 +08:00
|
|
|
|
label = '50';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
|
onTap: claimable
|
|
|
|
|
|
? () => _claimIndirectReferralBonus(
|
|
|
|
|
|
directReferralId,
|
|
|
|
|
|
indirectReferredUserId,
|
|
|
|
|
|
milestoneVal,
|
|
|
|
|
|
)
|
|
|
|
|
|
: null,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
|
color: bgColor,
|
|
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.sm),
|
|
|
|
|
|
border: claimable ? Border.all(color: textColor.withValues(alpha: 0.3)) : null,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
label,
|
|
|
|
|
|
style: AppTextStyles.bodySmall(context).copyWith(
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
|
color: textColor,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}).toList(),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 獎勵規則卡片
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildRulesCard(BuildContext context) {
|
2026-04-04 21:19:57 +08:00
|
|
|
|
return Container(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
decoration: BoxDecoration(
|
2026-04-06 01:58:08 +08:00
|
|
|
|
color: context.appColors.surfaceCardHigh,
|
2026-04-04 21:19:57 +08:00
|
|
|
|
borderRadius: BorderRadius.circular(AppRadius.lg),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Column(
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
|
children: [
|
2026-04-05 22:24:04 +08:00
|
|
|
|
Text(
|
2026-04-07 01:05:05 +08:00
|
|
|
|
'獎勵規則',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.headlineSmall(context),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
2026-04-05 22:24:04 +08:00
|
|
|
|
const SizedBox(height: 8),
|
2026-04-07 01:05:05 +08:00
|
|
|
|
_buildRuleItem('新用戶註冊完成實名認證獎勵 100 USDT'),
|
|
|
|
|
|
_buildRuleItem('邀請好友充值每達 1000 USDT,獎勵 100 USDT'),
|
|
|
|
|
|
_buildRuleItem('好友推廣的人充值每達 1000 USDT,額外獎勵 50 USDT'),
|
|
|
|
|
|
_buildRuleItem('獎勵直接發放至資金賬戶'),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:38:56 +08:00
|
|
|
|
Widget _buildRuleItem(String text) {
|
2026-04-04 21:19:57 +08:00
|
|
|
|
return Padding(
|
2026-04-05 22:24:04 +08:00
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 3),
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
'\u2022 $text',
|
2026-04-05 23:37:27 +08:00
|
|
|
|
style: AppTextStyles.bodyMedium(context).copyWith(
|
2026-04-08 12:24:24 +08:00
|
|
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
2026-04-05 22:24:04 +08:00
|
|
|
|
),
|
2026-04-04 21:19:57 +08:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-05 22:24:04 +08:00
|
|
|
|
// ============================================
|
2026-04-07 01:05:05 +08:00
|
|
|
|
// 業務邏輯
|
2026-04-05 22:24:04 +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-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showSuccess('領取成功!100 USDT 已到賬');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
_loadData();
|
|
|
|
|
|
} else {
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showError(response.message ?? '領取失敗');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showError('領取失敗: $e');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showSuccess('領取成功!100 USDT 已到賬');
|
2026-04-07 01:05:05 +08:00
|
|
|
|
_loadData();
|
|
|
|
|
|
} else {
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showError(response.message ?? '領取失敗');
|
2026-04-07 01:05:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showError('領取失敗: $e');
|
2026-04-07 01:05:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> _claimIndirectReferralBonus(
|
|
|
|
|
|
int directReferralId,
|
|
|
|
|
|
int indirectReferredUserId,
|
|
|
|
|
|
int milestone,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
try {
|
|
|
|
|
|
final bonusService = context.read<BonusService>();
|
|
|
|
|
|
final response = await bonusService.claimIndirectReferralBonus(
|
|
|
|
|
|
directReferralId,
|
|
|
|
|
|
indirectReferredUserId,
|
|
|
|
|
|
milestone,
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (response.success) {
|
|
|
|
|
|
context.read<AssetProvider>().refreshAll(force: true);
|
|
|
|
|
|
context.read<AppEventBus>().fire(AppEventType.assetChanged);
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showSuccess('領取成功!50 USDT 已到賬');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
_loadData();
|
|
|
|
|
|
} else {
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showError(response.message ?? '領取失敗');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2026-04-07 04:24:39 +08:00
|
|
|
|
ToastUtils.showError('領取失敗: $e');
|
2026-04-04 21:19:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|