gcx/frontend/genex-mobile/lib/features/profile/presentation/pages/share_page.dart

707 lines
26 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.

// ============================================================
// SharePage — 邀请好友 / 推广分享页
//
// 路由:/share从 ProfilePage「邀请好友」横幅入口跳转
//
// 功能概述:
// 1. Hero Card — 渐变卡片,包含:
// · 专属二维码qr_flutter 生成,编码 APK 直接下载链接)
// · 推荐码胶囊(可点击一键复制)
// 2. 邀请进度 — 显示直接推荐人数 + 团队总人数
// 3. 推荐奖励计划 — 营销区块:直接奖励 / 团队收益 / 新人福利
// 4. 我的推荐人信息 — 若当前用户是被推荐的,显示推荐人代码
// 5. 分享操作列表 — 三种快捷方式:复制推荐码 / 复制链接 / 原生分享
// 6. 主操作按钮 — 「分享给好友」触发系统原生分享弹层
//
// 二维码内容:
// 后端 admin-service 上传的 APK 直接下载链接
// 格式https://api.gogenex.com/api/v1/app/version/download/{id}
// 扫码即可直接下载安装,推荐码通过注册时手动填写进行归因。
//
// 分享文案(含推荐码 + APK 链接)用于营销推广。
//
// 依赖包:
// qr_flutter ^4.1.0 — 本地生成二维码,无网络依赖
// share_plus ^10.0.2 — 调用系统原生分享弹层
// ============================================================
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 '../../../../core/updater/update_service.dart';
import '../../../../shared/widgets/genex_button.dart';
import '../../../../app/i18n/app_localizations.dart';
/// A9. 邀请好友 / 推广分享页
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;
/// 后端 admin-service 上传的 APK 直接下载链接
/// 用于二维码内容,扫码即可直接下载安装包
String? _apkUrl;
// ── 计算属性 ─────────────────────────────────────────────────────────────
/// 二维码内容:优先用 APK 直链fallback 用 API 域名根路径
String get _qrContent => _apkUrl ?? 'https://api.gogenex.com';
/// 分享用链接APK 直链 或 fallback
String get _shareLink => _apkUrl ?? 'https://api.gogenex.com';
// ── 生命周期 ─────────────────────────────────────────────────────────────
@override
void initState() {
super.initState();
_loadData();
}
// ── 数据加载 ─────────────────────────────────────────────────────────────
/// 并发加载:推荐信息 + APK 下载链接
Future<void> _loadData() async {
setState(() {
_loading = true;
_error = null;
});
// 并发请求referral 信息 + APK 下载链接
final results = await Future.wait([
ReferralService.instance.getMyInfo().then<Object?>((v) => v).catchError((e) => e),
UpdateService().getLatestApkUrl().then<Object?>((v) => v).catchError((_) => null),
]);
if (!mounted) return;
final referralResult = results[0];
final apkUrlResult = results[1];
if (referralResult is ReferralInfo) {
setState(() {
_info = referralResult;
_apkUrl = apkUrlResult as String?;
_loading = false;
});
} else {
setState(() {
_error = referralResult.toString();
_apkUrl = apkUrlResult as String?;
_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: _shareLink));
if (mounted) _showCopied(context.t('share.linkCopied'));
}
/// 系统原生分享:营销文案 = APK 链接 + 推荐码 + 奖励说明
Future<void> _shareNative() async {
final code = _info?.referralCode ?? '';
// 优先用新的营销文案模板(含 {name} 占位符name 暂用 'Genex 用户'
final template = context.t('share.shareTextApk');
final text = template
.replaceAll('{name}', 'Genex 用户')
.replaceAll('{code}', code)
.replaceAll('{link}', _shareLink);
await Share.share(text, subject: context.t('share.nativeShareTitle'));
}
// ── Build ────────────────────────────────────────────────────────────────
@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 && _info == 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: _loadData,
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(), // QR 码APK 链接)+ 推荐码
const SizedBox(height: 16),
_buildStatsCard(), // 邀请进度
const SizedBox(height: 16),
_buildRewardPlanCard(), // 推荐奖励计划(营销区块)
const SizedBox(height: 16),
if (_info?.usedCode != null) ...[
_buildReferrerCard(), // 我的推荐人
const SizedBox(height: 16),
],
_buildShareActions(), // 快捷操作
const SizedBox(height: 24),
GenexButton(
label: context.t('share.shareToFriend'),
onPressed: _shareNative,
),
const SizedBox(height: 40),
],
),
);
}
// ── Hero CardAPK 二维码 + 推荐码 ────────────────────────────────────────
//
// 二维码内容 = APK 直接下载链接admin-service 流式返回)
// 扫码即触发 Android 下载安装iOS 浏览器打开提示
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.scanToDownload'),
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: 8),
// APK 正在加载时显示提示
if (_apkUrl == null)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(
color: Colors.white54,
strokeWidth: 1.5,
),
),
const SizedBox(width: 6),
Text(
context.t('share.apkUrlLoading'),
style: AppTypography.caption.copyWith(color: Colors.white54),
),
],
),
),
const SizedBox(height: 8),
// ── 二维码(编码 APK 下载链接)────────────────────────────────
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: _qrContent,
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: 20),
// ── 推荐码胶囊(单独展示,注册时手动填写用于归因)──────────────
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,
),
),
],
),
),
],
),
),
),
// ── 说明文字 ─────────────────────────────────────────────────
const SizedBox(height: 12),
Text(
context.t('share.joinWith')
.replaceAll('{code}', _info?.referralCode ?? ''),
style: AppTypography.caption.copyWith(color: Colors.white54),
textAlign: TextAlign.center,
),
],
),
),
);
}
// ── 邀请进度统计卡片 ─────────────────────────────────────────────────────
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)),
],
);
}
// ── 推荐奖励计划(营销区块)──────────────────────────────────────────────
//
// 展示推荐系统的三大核心收益,用于说服用户积极分享:
// 1. 直接推荐奖励 — 每推荐一人注册即得奖励
// 2. 团队层级收益 — 最高 50 层关系链收益
// 3. 新人专属福利 — 被推荐用户的注册礼包
Widget _buildRewardPlanCard() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 10),
child: Text(
context.t('share.rewardPlanTitle'),
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: [
_buildRewardItem(
icon: Icons.monetization_on_rounded,
iconBg: const Color(0xFFFFF3E0),
iconColor: const Color(0xFFFF9800),
title: context.t('share.rewardDirect'),
desc: context.t('share.rewardDirectDesc'),
),
const Divider(indent: 60, height: 1),
_buildRewardItem(
icon: Icons.account_tree_rounded,
iconBg: AppColors.infoLight,
iconColor: AppColors.info,
title: context.t('share.rewardTeam'),
desc: context.t('share.rewardTeamDesc'),
),
const Divider(indent: 60, height: 1),
_buildRewardItem(
icon: Icons.card_giftcard_rounded,
iconBg: AppColors.successLight,
iconColor: AppColors.success,
title: context.t('share.rewardNewbie'),
desc: context.t('share.rewardNewbieDesc'),
),
],
),
),
],
);
}
Widget _buildRewardItem({
required IconData icon,
required Color iconBg,
required Color iconColor,
required String title,
required String desc,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 38,
height: 38,
decoration: BoxDecoration(color: iconBg, borderRadius: BorderRadius.circular(10)),
child: Icon(icon, size: 20, color: iconColor),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: AppTypography.bodyMedium.copyWith(fontWeight: FontWeight.w600)),
const SizedBox(height: 2),
Text(
desc,
style: AppTypography.caption.copyWith(color: AppColors.textSecondary),
),
],
),
),
],
),
);
}
// ── 我的推荐人 ───────────────────────────────────────────────────────────
//
// 仅在 _info.usedCode != null 时显示(即当前用户是通过推荐码注册的)
Widget _buildReferrerCard() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: AppSpacing.borderRadiusMd,
border: Border.all(color: AppColors.borderLight),
boxShadow: AppSpacing.shadowSm,
),
child: Row(
children: [
Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: AppColors.primaryContainer,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.person_add_rounded, size: 20, color: AppColors.primary),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.t('share.myReferrer'),
style: AppTypography.caption.copyWith(color: AppColors.textTertiary),
),
const SizedBox(height: 2),
Text(
_info?.usedCode ?? context.t('share.noReferrer'),
style: AppTypography.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 1.5,
fontFamily: 'monospace',
),
),
],
),
),
],
),
);
}
// ── 分享操作列表 ─────────────────────────────────────────────────────────
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: _shareLink,
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,
);
}
}