459 lines
15 KiB
Dart
459 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:qr_flutter/qr_flutter.dart';
|
||
import 'package:share_plus/share_plus.dart';
|
||
import '../../../../app/theme/app_colors.dart';
|
||
import '../../../../app/theme/app_typography.dart';
|
||
import '../../../../app/theme/app_spacing.dart';
|
||
import '../../../../core/services/referral_service.dart';
|
||
import '../../../../shared/widgets/genex_button.dart';
|
||
import '../../../../app/i18n/app_localizations.dart';
|
||
|
||
/// A9. 邀请好友 / 推广分享页
|
||
///
|
||
/// 功能:
|
||
/// - 专属推荐码二维码(扫码跳转到 App 下载页并自动填入推荐码)
|
||
/// - 一键复制推荐码 / 复制链接
|
||
/// - 系统原生分享(微信、WhatsApp、Telegram、短信、邮件……)
|
||
/// - 邀请进度(直接推荐人数 + 团队总人数)
|
||
class SharePage extends StatefulWidget {
|
||
const SharePage({super.key});
|
||
|
||
@override
|
||
State<SharePage> createState() => _SharePageState();
|
||
}
|
||
|
||
class _SharePageState extends State<SharePage> {
|
||
ReferralInfo? _info;
|
||
bool _loading = true;
|
||
String? _error;
|
||
|
||
/// App 下载落地页(带推荐码参数)
|
||
static const String _baseInviteUrl = 'https://app.gogenex.com/download';
|
||
|
||
String get _inviteLink => _info != null
|
||
? '$_baseInviteUrl?ref=${_info!.referralCode}'
|
||
: _baseInviteUrl;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loadInfo();
|
||
}
|
||
|
||
Future<void> _loadInfo() async {
|
||
setState(() {
|
||
_loading = true;
|
||
_error = null;
|
||
});
|
||
try {
|
||
final info = await ReferralService.instance.getMyInfo();
|
||
if (mounted) setState(() { _info = info; _loading = false; });
|
||
} catch (e) {
|
||
if (mounted) setState(() { _error = e.toString(); _loading = false; });
|
||
}
|
||
}
|
||
|
||
void _showCopied(String message) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.check_circle_rounded, color: Colors.white, size: 18),
|
||
const SizedBox(width: 8),
|
||
Text(message),
|
||
],
|
||
),
|
||
backgroundColor: AppColors.primary,
|
||
behavior: SnackBarBehavior.floating,
|
||
duration: const Duration(seconds: 2),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _copyCode() async {
|
||
if (_info == null) return;
|
||
await Clipboard.setData(ClipboardData(text: _info!.referralCode));
|
||
if (mounted) _showCopied(context.t('share.codeCopied'));
|
||
}
|
||
|
||
Future<void> _copyLink() async {
|
||
await Clipboard.setData(ClipboardData(text: _inviteLink));
|
||
if (mounted) _showCopied(context.t('share.linkCopied'));
|
||
}
|
||
|
||
Future<void> _shareNative() async {
|
||
final code = _info?.referralCode ?? '';
|
||
final text = context
|
||
.t('share.shareText')
|
||
.replaceAll('{code}', code)
|
||
.replaceAll('{link}', _inviteLink);
|
||
await Share.share(text, subject: context.t('share.nativeShareTitle'));
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: AppColors.background,
|
||
appBar: AppBar(
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
title: Text(context.t('share.title')),
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
surfaceTintColor: Colors.transparent,
|
||
),
|
||
body: _loading
|
||
? _buildLoading()
|
||
: _error != null
|
||
? _buildError()
|
||
: _buildContent(),
|
||
);
|
||
}
|
||
|
||
Widget _buildLoading() {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const CircularProgressIndicator(),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
context.t('share.loading'),
|
||
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildError() {
|
||
return Center(
|
||
child: Padding(
|
||
padding: AppSpacing.pagePadding,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.error_outline_rounded, size: 56, color: AppColors.textTertiary),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
context.t('share.loadFailed'),
|
||
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
const SizedBox(height: 20),
|
||
TextButton.icon(
|
||
onPressed: _loadInfo,
|
||
icon: const Icon(Icons.refresh_rounded),
|
||
label: Text(context.t('share.retry')),
|
||
style: TextButton.styleFrom(foregroundColor: AppColors.primary),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildContent() {
|
||
return SingleChildScrollView(
|
||
padding: AppSpacing.pagePadding,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
_buildHeroCard(),
|
||
const SizedBox(height: 16),
|
||
_buildStatsCard(),
|
||
const SizedBox(height: 16),
|
||
_buildShareActions(),
|
||
const SizedBox(height: 24),
|
||
GenexButton(
|
||
label: context.t('share.shareToFriend'),
|
||
onPressed: _shareNative,
|
||
),
|
||
const SizedBox(height: 40),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ── Hero Card: QR 码 + 推荐码 ─────────────────────────────────────────────
|
||
|
||
Widget _buildHeroCard() {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
gradient: AppColors.cardGradient,
|
||
borderRadius: BorderRadius.circular(20),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: AppColors.primary.withOpacity(0.35),
|
||
blurRadius: 24,
|
||
offset: const Offset(0, 8),
|
||
),
|
||
],
|
||
),
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28),
|
||
child: Column(
|
||
children: [
|
||
// 顶部标题
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(Icons.auto_awesome_rounded, color: Colors.white60, size: 16),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
context.t('share.scanToJoin'),
|
||
style: AppTypography.bodyMedium.copyWith(color: Colors.white70),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 6),
|
||
Text(
|
||
'Genex',
|
||
style: AppTypography.displayMedium.copyWith(
|
||
color: Colors.white,
|
||
letterSpacing: 3,
|
||
fontWeight: FontWeight.w800,
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
|
||
// QR 码卡片
|
||
Container(
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withOpacity(0.12),
|
||
blurRadius: 16,
|
||
offset: const Offset(0, 4),
|
||
),
|
||
],
|
||
),
|
||
child: QrImageView(
|
||
data: _inviteLink,
|
||
version: QrVersions.auto,
|
||
size: 180,
|
||
backgroundColor: Colors.white,
|
||
eyeStyle: const QrEyeStyle(
|
||
eyeShape: QrEyeShape.square,
|
||
color: Color(0xFF4834D4),
|
||
),
|
||
dataModuleStyle: const QrDataModuleStyle(
|
||
dataModuleShape: QrDataModuleShape.square,
|
||
color: Color(0xFF6C5CE7),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
|
||
// 推荐码
|
||
Text(
|
||
context.t('share.myReferralCode'),
|
||
style: AppTypography.caption.copyWith(color: Colors.white60),
|
||
),
|
||
const SizedBox(height: 8),
|
||
GestureDetector(
|
||
onTap: _copyCode,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white.withOpacity(0.15),
|
||
borderRadius: BorderRadius.circular(40),
|
||
border: Border.all(color: Colors.white30),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text(
|
||
_info?.referralCode ?? '------',
|
||
style: AppTypography.h2.copyWith(
|
||
color: Colors.white,
|
||
letterSpacing: 4,
|
||
fontWeight: FontWeight.w700,
|
||
fontFamily: 'monospace',
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white.withOpacity(0.2),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(Icons.copy_rounded, size: 12, color: Colors.white),
|
||
const SizedBox(width: 4),
|
||
Text(
|
||
context.t('share.copyCode'),
|
||
style: AppTypography.caption.copyWith(
|
||
color: Colors.white,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ── 邀请进度 ──────────────────────────────────────────────────────────────
|
||
|
||
Widget _buildStatsCard() {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||
decoration: BoxDecoration(
|
||
color: AppColors.surface,
|
||
borderRadius: AppSpacing.borderRadiusMd,
|
||
border: Border.all(color: AppColors.borderLight),
|
||
boxShadow: AppSpacing.shadowSm,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildStatItem(
|
||
icon: Icons.people_rounded,
|
||
value: '${_info?.directReferralCount ?? 0}',
|
||
label: context.t('share.directReferrals'),
|
||
),
|
||
),
|
||
Container(width: 1, height: 44, color: AppColors.borderLight),
|
||
Expanded(
|
||
child: _buildStatItem(
|
||
icon: Icons.groups_rounded,
|
||
value: '${_info?.totalTeamCount ?? 0}',
|
||
label: context.t('share.teamSize'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildStatItem({
|
||
required IconData icon,
|
||
required String value,
|
||
required String label,
|
||
}) {
|
||
return Column(
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(icon, size: 16, color: AppColors.primary),
|
||
const SizedBox(width: 4),
|
||
Text(
|
||
value,
|
||
style: AppTypography.h2.copyWith(color: AppColors.primary),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
label,
|
||
style: AppTypography.caption.copyWith(color: AppColors.textSecondary),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
// ── 分享操作列表 ───────────────────────────────────────────────────────────
|
||
|
||
Widget _buildShareActions() {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(left: 4, bottom: 10),
|
||
child: Text(
|
||
context.t('share.quickShare'),
|
||
style: AppTypography.labelSmall.copyWith(color: AppColors.textTertiary),
|
||
),
|
||
),
|
||
Container(
|
||
decoration: BoxDecoration(
|
||
color: AppColors.surface,
|
||
borderRadius: AppSpacing.borderRadiusMd,
|
||
border: Border.all(color: AppColors.borderLight),
|
||
boxShadow: AppSpacing.shadowSm,
|
||
),
|
||
child: Column(
|
||
children: [
|
||
_buildActionTile(
|
||
icon: Icons.qr_code_rounded,
|
||
iconBg: AppColors.primaryContainer,
|
||
iconColor: AppColors.primary,
|
||
title: context.t('share.copyCode'),
|
||
subtitle: _info?.referralCode ?? '',
|
||
onTap: _copyCode,
|
||
),
|
||
const Divider(indent: 60, height: 1),
|
||
_buildActionTile(
|
||
icon: Icons.link_rounded,
|
||
iconBg: AppColors.infoLight,
|
||
iconColor: AppColors.info,
|
||
title: context.t('share.copyLink'),
|
||
subtitle: _inviteLink,
|
||
onTap: _copyLink,
|
||
),
|
||
const Divider(indent: 60, height: 1),
|
||
_buildActionTile(
|
||
icon: Icons.share_rounded,
|
||
iconBg: AppColors.successLight,
|
||
iconColor: AppColors.success,
|
||
title: context.t('share.shareToFriend'),
|
||
subtitle: context.t('share.shareSubtitle'),
|
||
onTap: _shareNative,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildActionTile({
|
||
required IconData icon,
|
||
required Color iconBg,
|
||
required Color iconColor,
|
||
required String title,
|
||
required String subtitle,
|
||
required VoidCallback onTap,
|
||
}) {
|
||
return ListTile(
|
||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||
leading: Container(
|
||
width: 38,
|
||
height: 38,
|
||
decoration: BoxDecoration(color: iconBg, borderRadius: BorderRadius.circular(10)),
|
||
child: Icon(icon, size: 20, color: iconColor),
|
||
),
|
||
title: Text(title, style: AppTypography.bodyMedium),
|
||
subtitle: Text(
|
||
subtitle,
|
||
style: AppTypography.caption.copyWith(color: AppColors.textTertiary),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
trailing: const Icon(Icons.chevron_right_rounded, color: AppColors.textTertiary, size: 20),
|
||
onTap: onTap,
|
||
);
|
||
}
|
||
}
|