// ============================================================ // 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 createState() => _SharePageState(); } class _SharePageState extends State { // ── 状态 ──────────────────────────────────────────────────────────────── 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 _loadData() async { setState(() { _loading = true; _error = null; }); // 并发请求:referral 信息 + APK 下载链接 final results = await Future.wait([ ReferralService.instance.getMyInfo().then((v) => v).catchError((e) => e), UpdateService().getLatestApkUrl().then((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 _copyCode() async { if (_info == null) return; await Clipboard.setData(ClipboardData(text: _info!.referralCode)); if (mounted) _showCopied(context.t('share.codeCopied')); } Future _copyLink() async { await Clipboard.setData(ClipboardData(text: _shareLink)); if (mounted) _showCopied(context.t('share.linkCopied')); } /// 系统原生分享:营销文案 = APK 链接 + 推荐码 + 奖励说明 Future _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 Card:APK 二维码 + 推荐码 ──────────────────────────────────────── // // 二维码内容 = 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, ); } }