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

631 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.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 infoAsync = ref.watch(referralInfoProvider);
final isDark = Theme.of(context).brightness == Brightness.dark;
final cardColor = isDark ? AppColors.surface : Colors.white;
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
backgroundColor: AppColors.background,
title: const Text('邀请有礼'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
ref.invalidate(referralInfoProvider);
ref.invalidate(referralListProvider);
ref.invalidate(pendingRewardsProvider);
},
),
],
),
body: infoAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
data: (info) => _ReferralBody(info: info, cardColor: cardColor),
),
);
}
}
class _ReferralBody extends ConsumerWidget {
final ReferralInfo info;
final Color cardColor;
const _ReferralBody({required this.info, required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
// ── Referral Code Card ─────────────────────────────────────
_ReferralCodeCard(info: info, cardColor: cardColor),
const SizedBox(height: 16),
// ── Stats Row ─────────────────────────────────────────────
_StatsRow(info: info, cardColor: cardColor),
const SizedBox(height: 20),
// ── Reward Rules ──────────────────────────────────────────
_RewardRulesCard(cardColor: cardColor),
const SizedBox(height: 20),
// ── Referral List ─────────────────────────────────────────
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'推荐记录',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
TextButton(
onPressed: () => _showReferralList(context),
child: const Text('查看全部 >'),
),
],
),
_ReferralPreviewList(cardColor: cardColor),
const SizedBox(height: 20),
// ── Reward List ───────────────────────────────────────────
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'待领积分',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
TextButton(
onPressed: () => _showRewardList(context),
child: const Text('查看全部 >'),
),
],
),
_RewardPreviewList(cardColor: cardColor),
const SizedBox(height: 40),
],
);
}
void _showReferralList(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const _ReferralListPage()),
);
}
void _showRewardList(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const _RewardListPage()),
);
}
}
// ── Referral Code Card ────────────────────────────────────────────────────────
class _ReferralCodeCard extends StatelessWidget {
final ReferralInfo info;
final Color cardColor;
const _ReferralCodeCard({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.referralCode,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
letterSpacing: 2,
color: AppColors.primary,
),
),
),
IconButton(
icon: const Icon(Icons.copy, color: AppColors.primary),
tooltip: '复制推荐码',
onPressed: () => _copy(context, info.referralCode),
),
],
),
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: 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: const Text('分享'),
onPressed: () => _share(context, info),
style: FilledButton.styleFrom(
backgroundColor: AppColors.primary,
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, ReferralInfo info) {
// For a full implementation, use share_plus package
// For now, copy the share text
final text = '邀请你使用 IT0 智能运维平台,注册即可获得积分奖励!\n推荐码:${info.referralCode}\n链接:${info.shareUrl}';
_copy(context, text);
}
}
// ── Stats Row ─────────────────────────────────────────────────────────────────
class _StatsRow extends StatelessWidget {
final ReferralInfo info;
final Color cardColor;
const _StatsRow({required this.info, required this.cardColor});
@override
Widget build(BuildContext context) {
return Row(
children: [
_StatCard(
cardColor: cardColor,
label: '已推荐',
value: '${info.directCount}',
unit: '',
color: const Color(0xFF6366F1),
),
const SizedBox(width: 10),
_StatCard(
cardColor: cardColor,
label: '已激活',
value: '${info.activeCount}',
unit: '',
color: const Color(0xFF10B981),
),
const SizedBox(width: 10),
_StatCard(
cardColor: cardColor,
label: '待领积分',
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)),
),
],
),
),
],
),
),
),
);
}
}
// ── Reward Rules Card ─────────────────────────────────────────────────────────
class _RewardRulesCard extends StatelessWidget {
final Color cardColor;
const _RewardRulesCard({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.card_giftcard, color: Color(0xFFF59E0B), size: 20),
SizedBox(width: 6),
Text(
'奖励规则',
style: 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)),
),
],
);
}
}
// ── Preview Lists ─────────────────────────────────────────────────────────────
class _ReferralPreviewList extends ConsumerWidget {
final Color cardColor;
const _ReferralPreviewList({required this.cardColor});
@override
Widget build(BuildContext context, WidgetRef ref) {
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 Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: const Padding(
padding: EdgeInsets.all(20),
child: Center(
child: Text(
'暂无推荐记录,分享推荐码邀请好友吧',
style: TextStyle(color: Colors.grey, fontSize: 13),
),
),
),
);
}
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 statusColor = item.isActive
? const Color(0xFF10B981)
: item.status == 'EXPIRED'
? Colors.grey
: const Color(0xFFF59E0B);
final statusLabel = switch (item.status) {
'PENDING' => '待付款',
'ACTIVE' => '已激活',
'REWARDED' => '已奖励',
'EXPIRED' => '已过期',
_ => 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(
'注册于 ${_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 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 Card(
color: cardColor,
elevation: 0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: const Padding(
padding: EdgeInsets.all(20),
child: Center(
child: Text(
'暂无待领积分',
style: TextStyle(color: Colors.grey, fontSize: 13),
),
),
),
);
}
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) {
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: const Text(
'待抵扣',
style: TextStyle(
fontSize: 12,
color: Color(0xFFF59E0B),
fontWeight: FontWeight.w500),
),
),
);
}
}
// ── Full list pages ───────────────────────────────────────────────────────────
class _ReferralListPage extends ConsumerWidget {
const _ReferralListPage();
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(referralListProvider);
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) => _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]),
),
),
);
}
}