it0/it0_app/lib/features/referral/presentation/screens/referral_screen.dart

1170 lines
39 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:flutter_riverpod/flutter_riverpod.dart';
import 'package:it0_app/l10n/app_localizations.dart';
import '../../../../core/theme/app_colors.dart';
import '../providers/referral_providers.dart';
import '../../domain/models/referral_info.dart';
class ReferralScreen extends ConsumerWidget {
const ReferralScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final isDark = Theme.of(context).brightness == Brightness.dark;
final cardColor = isDark ? AppColors.surface : Colors.white;
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
backgroundColor: AppColors.background,
title: Text(l10n.referralScreenTitle),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
ref.invalidate(referralInfoProvider);
ref.invalidate(referralListProvider);
ref.invalidate(pendingRewardsProvider);
ref.invalidate(userReferralInfoProvider);
ref.invalidate(myCircleProvider);
ref.invalidate(myPointsProvider);
},
),
],
bottom: TabBar(
indicatorColor: AppColors.primary,
labelColor: AppColors.primary,
unselectedLabelColor: Colors.grey,
tabs: const [
Tab(text: '企业推荐'),
Tab(text: '我的圈子'),
],
),
),
body: TabBarView(
children: [
_TenantReferralTab(cardColor: cardColor),
_PersonalCircleTab(cardColor: cardColor),
],
),
),
);
}
}
// ══════════════════════════════════════════════════════════════════════════════
// Tab 1 — Tenant / B2B referral (existing)
// ══════════════════════════════════════════════════════════════════════════════
class _TenantReferralTab extends ConsumerWidget {
final Color cardColor;
const _TenantReferralTab({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
final infoAsync = ref.watch(referralInfoProvider);
return infoAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (info) => ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
_ReferralCodeCard(info: info, cardColor: cardColor),
const SizedBox(height: 16),
_StatsRow(info: info, cardColor: cardColor),
const SizedBox(height: 20),
_RewardRulesCard(cardColor: cardColor),
const SizedBox(height: 20),
_SectionHeader(
title: '推荐记录',
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const _ReferralListPage())),
),
_ReferralPreviewList(cardColor: cardColor),
const SizedBox(height: 20),
_SectionHeader(
title: '待结算奖励',
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const _RewardListPage())),
),
_RewardPreviewList(cardColor: cardColor),
const SizedBox(height: 40),
],
),
);
}
}
// ══════════════════════════════════════════════════════════════════════════════
// Tab 2 — Personal circle / C2C
// ══════════════════════════════════════════════════════════════════════════════
class _PersonalCircleTab extends ConsumerWidget {
final Color cardColor;
const _PersonalCircleTab({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
final infoAsync = ref.watch(userReferralInfoProvider);
return infoAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (info) => ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
_UserCodeCard(info: info, cardColor: cardColor),
const SizedBox(height: 16),
_PointsBalanceCard(info: info, cardColor: cardColor),
const SizedBox(height: 20),
_CircleRulesCard(cardColor: cardColor),
const SizedBox(height: 20),
_SectionHeader(
title: '我的圈子成员',
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const _CircleListPage())),
),
_CirclePreviewList(cardColor: cardColor),
const SizedBox(height: 20),
_SectionHeader(
title: '积分记录',
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const _PointsHistoryPage())),
),
_PointsPreviewList(cardColor: cardColor),
const SizedBox(height: 40),
],
),
);
}
}
// ── User Referral Code Card ───────────────────────────────────────────────────
class _UserCodeCard extends StatelessWidget {
final UserReferralInfo info;
final Color cardColor;
const _UserCodeCard({required this.info, required this.cardColor});
@override
Widget build(BuildContext context) {
return Card(
color: cardColor,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'我的个人邀请码',
style: TextStyle(color: Colors.grey, fontSize: 13),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: Text(
info.code,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
letterSpacing: 2,
color: Color(0xFF7C3AED),
),
),
),
IconButton(
icon: const Icon(Icons.copy, color: Color(0xFF7C3AED)),
tooltip: '复制邀请码',
onPressed: () => _copy(context, info.code),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.link, size: 18),
label: const Text('复制邀请链接'),
onPressed: () => _copy(context, info.shareUrl),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF7C3AED),
side: const BorderSide(color: Color(0xFF7C3AED)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
),
const SizedBox(width: 8),
Expanded(
child: FilledButton.icon(
icon: const Icon(Icons.share, size: 18),
label: const Text('分享'),
onPressed: () => _share(context, info),
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF7C3AED),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
),
],
),
],
),
),
);
}
void _copy(BuildContext context, String text) {
Clipboard.setData(ClipboardData(text: text));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已复制到剪贴板'),
duration: Duration(seconds: 2)),
);
}
void _share(BuildContext context, UserReferralInfo info) {
final text =
'邀请你加入 IT0 智能体管理平台用AI管理你的数字工作加入即获 200 积分奖励!\n邀请码:${info.code}\n链接:${info.shareUrl}';
_copy(context, text);
}
}
// ── Points Balance Card ───────────────────────────────────────────────────────
class _PointsBalanceCard extends StatelessWidget {
final UserReferralInfo info;
final Color cardColor;
const _PointsBalanceCard({required this.info, required this.cardColor});
@override
Widget build(BuildContext context) {
return Card(
color: cardColor,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.stars_rounded, color: Color(0xFFF59E0B), size: 20),
SizedBox(width: 6),
Text('积分余额',
style:
TextStyle(fontWeight: FontWeight.w600, fontSize: 15)),
],
),
const SizedBox(height: 16),
Row(
children: [
_PointsStatItem(
label: '当前余额',
value: '${info.pointsBalance}',
unit: 'pts',
color: const Color(0xFF7C3AED),
),
const SizedBox(width: 12),
_PointsStatItem(
label: '圈子成员',
value: '${info.circleSize}',
unit: '',
color: const Color(0xFF10B981),
),
const SizedBox(width: 12),
_PointsStatItem(
label: '累计获得',
value: '${info.totalEarned}',
unit: 'pts',
color: const Color(0xFF6366F1),
),
],
),
],
),
),
);
}
}
class _PointsStatItem extends StatelessWidget {
final String label;
final String value;
final String unit;
final Color color;
const _PointsStatItem({
required this.label,
required this.value,
required this.unit,
required this.color,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 4),
RichText(
text: TextSpan(
children: [
TextSpan(
text: value,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
TextSpan(
text: ' $unit',
style: TextStyle(fontSize: 12, color: color.withAlpha(180)),
),
],
),
),
],
),
);
}
}
// ── Circle Rules Card ─────────────────────────────────────────────────────────
class _CircleRulesCard extends StatelessWidget {
final Color cardColor;
const _CircleRulesCard({required this.cardColor});
@override
Widget build(BuildContext context) {
return Card(
color: cardColor,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.people_alt_rounded,
color: Color(0xFF7C3AED), size: 20),
SizedBox(width: 6),
Text('圈子奖励规则',
style:
TextStyle(fontWeight: FontWeight.w600, fontSize: 15)),
],
),
const SizedBox(height: 12),
_RuleItem(
icon: Icons.card_giftcard_rounded,
color: const Color(0xFF7C3AED),
text: '新成员加入你的圈子,你和对方各获 200 积分欢迎礼',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.star_rounded,
color: const Color(0xFF6366F1),
text: '圈子成员订阅 Pro 时,你获 1500 pts对方获 500 pts',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.star_rounded,
color: const Color(0xFF7C3AED),
text: '圈子成员订阅 Enterprise 时,你获 5000 pts对方获 2000 pts',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.repeat_rounded,
color: const Color(0xFF10B981),
text: '每月续订时你持续获得付款额 10% 的积分,最长 12 个月',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.account_tree_rounded,
color: const Color(0xFFF59E0B),
text: '二级圈子续订,你获 5% 积分,最长 6 个月',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.redeem_rounded,
color: const Color(0xFF10B981),
text: '积分可兑换额外使用配额或解锁智能体',
),
],
),
),
);
}
}
// ── Circle Member Preview ─────────────────────────────────────────────────────
class _CirclePreviewList extends ConsumerWidget {
final Color cardColor;
const _CirclePreviewList({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(myCircleProvider);
return async.when(
loading: () => const SizedBox(
height: 60, child: Center(child: CircularProgressIndicator())),
error: (_, __) => const SizedBox.shrink(),
data: (result) {
if (result.items.isEmpty) {
return _EmptyCard(cardColor: cardColor, message: '暂无圈子成员,快去邀请好友吧!');
}
final preview = result.items.take(3).toList();
return Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: Clip.antiAlias,
child: Column(
children: preview.map((m) => _CircleMemberTile(member: m)).toList(),
),
);
},
);
}
}
class _CircleMemberTile extends StatelessWidget {
final CircleMember member;
const _CircleMemberTile({required this.member});
@override
Widget build(BuildContext context) {
final statusColor = member.isActive
? const Color(0xFF10B981)
: member.status == 'EXPIRED'
? Colors.grey
: const Color(0xFFF59E0B);
final statusLabel = switch (member.status) {
'PENDING' => '待激活',
'ACTIVE' => '已激活',
'REWARDED' => '已奖励',
'EXPIRED' => '已过期',
_ => member.status,
};
final levelLabel = member.level == 1 ? 'L1' : 'L2';
return ListTile(
leading: CircleAvatar(
backgroundColor: const Color(0xFF7C3AED).withAlpha(20),
child: Text(
levelLabel,
style: const TextStyle(
color: Color(0xFF7C3AED),
fontWeight: FontWeight.bold,
fontSize: 12),
),
),
title: Text(
member.referredUserId.length > 8
? '${member.referredUserId.substring(0, 8)}...'
: member.referredUserId,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
subtitle: Text(
'加入于 ${_formatDate(member.joinedAt)}',
style: const TextStyle(fontSize: 12),
),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withAlpha(20),
borderRadius: BorderRadius.circular(20),
),
child: Text(
statusLabel,
style: TextStyle(
fontSize: 12,
color: statusColor,
fontWeight: FontWeight.w500),
),
),
);
}
String _formatDate(DateTime dt) =>
'${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}';
}
// ── Points Preview List ───────────────────────────────────────────────────────
class _PointsPreviewList extends ConsumerWidget {
final Color cardColor;
const _PointsPreviewList({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(myPointsProvider);
return async.when(
loading: () => const SizedBox(
height: 60, child: Center(child: CircularProgressIndicator())),
error: (_, __) => const SizedBox.shrink(),
data: (result) {
if (result.transactions.isEmpty) {
return _EmptyCard(cardColor: cardColor, message: '暂无积分记录');
}
final preview = result.transactions.take(3).toList();
return Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: Clip.antiAlias,
child: Column(
children:
preview.map((t) => _PointsTile(tx: t)).toList(),
),
);
},
);
}
}
class _PointsTile extends StatelessWidget {
final PointTransaction tx;
const _PointsTile({required this.tx});
@override
Widget build(BuildContext context) {
final color =
tx.isEarned ? const Color(0xFF10B981) : const Color(0xFFEF4444);
final sign = tx.isEarned ? '+' : '';
return ListTile(
leading: CircleAvatar(
backgroundColor: color.withAlpha(20),
child: Icon(
tx.isEarned ? Icons.add_circle_outline : Icons.remove_circle_outline,
color: color,
size: 20,
),
),
title: Text(tx.typeLabel,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
subtitle: Text(
_formatDate(tx.createdAt),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
trailing: Text(
'$sign${tx.delta} pts',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: color),
),
);
}
String _formatDate(DateTime dt) =>
'${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}';
}
// ── Full list pages (circle & points) ────────────────────────────────────────
class _CircleListPage extends ConsumerWidget {
const _CircleListPage();
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(myCircleProvider);
return Scaffold(
appBar: AppBar(title: const Text('我的圈子')),
body: async.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (result) => result.items.isEmpty
? const Center(child: Text('暂无圈子成员'))
: ListView.separated(
itemCount: result.items.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) =>
_CircleMemberTile(member: result.items[i]),
),
),
);
}
}
class _PointsHistoryPage extends ConsumerWidget {
const _PointsHistoryPage();
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(myPointsProvider);
return Scaffold(
appBar: AppBar(title: const Text('积分记录')),
body: async.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (result) => result.transactions.isEmpty
? const Center(child: Text('暂无积分记录'))
: ListView.separated(
itemCount: result.transactions.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) =>
_PointsTile(tx: result.transactions[i]),
),
),
);
}
}
// ══════════════════════════════════════════════════════════════════════════════
// Shared / Tenant tab widgets (unchanged logic, extracted)
// ══════════════════════════════════════════════════════════════════════════════
class _ReferralCodeCard extends StatelessWidget {
final ReferralInfo info;
final Color cardColor;
const _ReferralCodeCard({required this.info, required this.cardColor});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Card(
color: cardColor,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.yourReferralCodeLabel,
style: const TextStyle(color: Colors.grey, fontSize: 13),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: Text(
info.referralCode,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
letterSpacing: 2,
color: AppColors.primary,
),
),
),
IconButton(
icon: const Icon(Icons.copy, color: AppColors.primary),
tooltip: l10n.copyReferralCodeTooltip,
onPressed: () => _copy(context, info.referralCode),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.link, size: 18),
label: Text(l10n.copyInviteLinkButton),
onPressed: () => _copy(context, info.shareUrl),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primary,
side: const BorderSide(color: AppColors.primary),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
),
const SizedBox(width: 8),
Expanded(
child: FilledButton.icon(
icon: const Icon(Icons.share, size: 18),
label: Text(l10n.shareButton),
onPressed: () => _share(context, info),
style: FilledButton.styleFrom(
backgroundColor: AppColors.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
),
],
),
],
),
),
);
}
void _copy(BuildContext context, String text) {
final l10n = AppLocalizations.of(context);
Clipboard.setData(ClipboardData(text: text));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.copiedToClipboard),
duration: const Duration(seconds: 2)),
);
}
void _share(BuildContext context, ReferralInfo info) {
final text =
'邀请你使用 IT0 智能体管理平台,注册即可获得积分奖励!\n推荐码:${info.referralCode}\n链接:${info.shareUrl}';
_copy(context, text);
}
}
class _StatsRow extends StatelessWidget {
final ReferralInfo info;
final Color cardColor;
const _StatsRow({required this.info, required this.cardColor});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Row(
children: [
_StatCard(
cardColor: cardColor,
label: l10n.referredLabel,
value: '${info.directCount}',
unit: l10n.peopleUnit,
color: const Color(0xFF6366F1),
),
const SizedBox(width: 10),
_StatCard(
cardColor: cardColor,
label: l10n.activatedLabel,
value: '${info.activeCount}',
unit: l10n.peopleUnit,
color: const Color(0xFF10B981),
),
const SizedBox(width: 10),
_StatCard(
cardColor: cardColor,
label: l10n.pendingCreditsLabel,
value: info.pendingCreditFormatted,
unit: '',
color: const Color(0xFFF59E0B),
),
],
);
}
}
class _StatCard extends StatelessWidget {
final Color cardColor;
final String label;
final String value;
final String unit;
final Color color;
const _StatCard({
required this.cardColor,
required this.label,
required this.value,
required this.unit,
required this.color,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: Card(
color: cardColor,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: const TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 4),
RichText(
text: TextSpan(
children: [
TextSpan(
text: value,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: color,
),
),
if (unit.isNotEmpty)
TextSpan(
text: unit,
style:
TextStyle(fontSize: 13, color: color.withAlpha(180)),
),
],
),
),
],
),
),
),
);
}
}
class _RewardRulesCard extends StatelessWidget {
final Color cardColor;
const _RewardRulesCard({required this.cardColor});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Card(
color: cardColor,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.card_giftcard,
color: Color(0xFFF59E0B), size: 20),
const SizedBox(width: 6),
Text(
l10n.rewardRulesTitle,
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 15),
),
],
),
const SizedBox(height: 12),
_RuleItem(
icon: Icons.star_rounded,
color: const Color(0xFF6366F1),
text: '推荐 Pro 套餐:你获得 \$15 积分,对方获得 \$5 积分',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.star_rounded,
color: const Color(0xFF7C3AED),
text: '推荐 Enterprise 套餐:你获得 \$50 积分,对方获得 \$20 积分',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.repeat_rounded,
color: const Color(0xFF10B981),
text: '对方续订后,你持续获得每月付款额 10% 的积分,最长 12 个月',
),
const SizedBox(height: 8),
_RuleItem(
icon: Icons.account_balance_wallet_outlined,
color: const Color(0xFFF59E0B),
text: '积分自动抵扣你的下期账单',
),
],
),
),
);
}
}
class _RuleItem extends StatelessWidget {
final IconData icon;
final Color color;
final String text;
const _RuleItem({required this.icon, required this.color, required this.text});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: color, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(text, style: const TextStyle(fontSize: 13, height: 1.4)),
),
],
);
}
}
class _SectionHeader extends StatelessWidget {
final String title;
final VoidCallback onTap;
const _SectionHeader({required this.title, required this.onTap});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title,
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 16)),
TextButton(onPressed: onTap, child: const Text('查看全部')),
],
);
}
}
class _EmptyCard extends StatelessWidget {
final Color cardColor;
final String message;
const _EmptyCard({required this.cardColor, required this.message});
@override
Widget build(BuildContext context) {
return Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Text(message,
style: const TextStyle(color: Colors.grey, fontSize: 13)),
),
),
);
}
}
class _ReferralPreviewList extends ConsumerWidget {
final Color cardColor;
const _ReferralPreviewList({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final async = ref.watch(referralListProvider);
return async.when(
loading: () => const SizedBox(
height: 60, child: Center(child: CircularProgressIndicator())),
error: (_, __) => const SizedBox.shrink(),
data: (result) {
if (result.items.isEmpty) {
return _EmptyCard(
cardColor: cardColor, message: l10n.noReferralsMessage);
}
final preview = result.items.take(3).toList();
return Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: Clip.antiAlias,
child: Column(
children: preview.map((item) => _ReferralTile(item: item)).toList(),
),
);
},
);
}
}
class _ReferralTile extends StatelessWidget {
final ReferralItem item;
const _ReferralTile({required this.item});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final statusColor = item.isActive
? const Color(0xFF10B981)
: item.status == 'EXPIRED'
? Colors.grey
: const Color(0xFFF59E0B);
final statusLabel = switch (item.status) {
'PENDING' => l10n.pendingPaymentStatus,
'ACTIVE' => l10n.activeStatus,
'REWARDED' => l10n.rewardedStatus,
'EXPIRED' => l10n.expiredStatus,
_ => item.status,
};
return ListTile(
leading: CircleAvatar(
backgroundColor: statusColor.withAlpha(30),
child: Icon(
item.isActive ? Icons.check_circle : Icons.pending,
color: statusColor,
size: 20,
),
),
title: Text(
item.referredTenantId.length > 8
? '${item.referredTenantId.substring(0, 8)}...'
: item.referredTenantId,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
subtitle: Text(
'${l10n.registeredAt} ${_formatDate(item.registeredAt)}',
style: const TextStyle(fontSize: 12),
),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withAlpha(20),
borderRadius: BorderRadius.circular(20),
),
child: Text(
statusLabel,
style: TextStyle(
fontSize: 12,
color: statusColor,
fontWeight: FontWeight.w500),
),
),
);
}
String _formatDate(DateTime dt) =>
'${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}';
}
class _RewardPreviewList extends ConsumerWidget {
final Color cardColor;
const _RewardPreviewList({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final async = ref.watch(pendingRewardsProvider);
return async.when(
loading: () => const SizedBox(
height: 60, child: Center(child: CircularProgressIndicator())),
error: (_, __) => const SizedBox.shrink(),
data: (result) {
if (result.items.isEmpty) {
return _EmptyCard(
cardColor: cardColor, message: l10n.noPendingRewardsMessage);
}
final preview = result.items.take(3).toList();
return Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: Clip.antiAlias,
child: Column(
children: preview.map((item) => _RewardTile(item: item)).toList(),
),
);
},
);
}
}
class _RewardTile extends StatelessWidget {
final RewardItem item;
const _RewardTile({required this.item});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return ListTile(
leading: CircleAvatar(
backgroundColor: const Color(0xFFF59E0B).withAlpha(30),
child: const Icon(Icons.attach_money,
color: Color(0xFFF59E0B), size: 20),
),
title: Text(
item.amountFormatted,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF10B981)),
),
subtitle: Text(item.triggerLabel,
style: const TextStyle(fontSize: 12)),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFFF59E0B).withAlpha(20),
borderRadius: BorderRadius.circular(20),
),
child: Text(
l10n.pendingDeductionStatus,
style: const TextStyle(
fontSize: 12,
color: Color(0xFFF59E0B),
fontWeight: FontWeight.w500),
),
),
);
}
}
class _ReferralListPage extends ConsumerWidget {
const _ReferralListPage();
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final async = ref.watch(referralListProvider);
return Scaffold(
appBar: AppBar(title: Text(l10n.referralRecordsSection)),
body: async.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (result) => result.items.isEmpty
? Center(child: Text(l10n.noReferralsMessage))
: ListView.separated(
itemCount: result.items.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) => _ReferralTile(item: result.items[i]),
),
),
);
}
}
class _RewardListPage extends ConsumerWidget {
const _RewardListPage();
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(allRewardsProvider);
return Scaffold(
appBar: AppBar(title: const Text('奖励历史')),
body: async.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (result) => result.items.isEmpty
? const Center(child: Text('暂无奖励记录'))
: ListView.separated(
itemCount: result.items.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) => _RewardTile(item: result.items[i]),
),
),
);
}
}