// ============================================================ // SharePage — 邀请好友 / 推广分享页 // // 路由:/share(从 ProfilePage「邀请好友」横幅入口跳转) // // 功能概述: // 1. Hero Card — 紫色渐变卡片,包含: // · 专属二维码(qr_flutter 生成,编码邀请落地页 URL) // · 推荐码胶囊(可点击一键复制) // 2. 邀请进度 — 显示直接推荐人数 + 团队总人数 // 3. 分享操作列表 — 三种快捷方式:复制推荐码 / 复制链接 / 原生分享 // 4. 主操作按钮 — 「分享给好友」触发系统原生分享弹层 // // 支持的分享场景(取决于用户设备安装的 App): // 微信 / QQ / WhatsApp / Telegram / Line / Twitter/X // 短信 / 邮件 / AirDrop / 复制到剪贴板 / 保存至文件…… // // 数据来源: // GET /api/v1/referral/my → ReferralService.getMyInfo() // 推荐码通过 Kafka 事件在用户注册时自动生成,首次打开此页若 // 数据尚未就绪,将显示 Loading → 加载失败 → 重试 流程。 // // 邀请链接格式: // https://app.gogenex.com/download?ref={referralCode} // 落地页检测设备后分别跳转 App Store / Google Play / 浏览器下载。 // ref 参数由注册流程自动读取并预填推荐码输入框。 // // 依赖包: // qr_flutter ^4.1.0 — 本地生成二维码,无网络依赖 // share_plus ^10.0.2 — 调用系统原生分享弹层(iOS Share Sheet / Android Sharesheet) // ============================================================ 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. 邀请好友 / 推广分享页 /// /// 页面生命周期: /// initState → _loadInfo() → [Loading] → [Content | Error] /// /// Widget 树概览: /// ``` /// Scaffold /// └── SingleChildScrollView /// ├── _buildHeroCard() 渐变卡片:QR 码 + 推荐码 /// ├── _buildStatsCard() 邀请进度:直接推荐 | 团队人数 /// ├── _buildShareActions() 操作列表:复制码 / 复制链 / 系统分享 /// └── GenexButton 主按钮:「分享给好友」 /// ``` class SharePage extends StatefulWidget { const SharePage({super.key}); @override State createState() => _SharePageState(); } class _SharePageState extends State { // ── 状态 ──────────────────────────────────────────────────────────────── /// 从 referral-service 加载的推荐信息;加载完成前为 null ReferralInfo? _info; /// 正在请求后端数据 bool _loading = true; /// 请求失败时的错误信息(用于展示重试按钮) String? _error; // ── 常量 ──────────────────────────────────────────────────────────────── /// App 下载落地页基础地址 /// /// 落地页职责: /// 1. 检测 UA(iOS / Android / 其他) /// 2. iOS → 跳转 App Store /// 3. Android → 跳转 Google Play 或直接下载 APK /// 4. 读取 `ref` 参数并存入 localStorage,注册页自动填充推荐码 static const String _baseInviteUrl = 'https://app.gogenex.com/download'; // ── 计算属性 ───────────────────────────────────────────────────────────── /// 完整邀请链接(二维码内容 + 分享文案使用) /// /// 已加载:https://app.gogenex.com/download?ref=GNXAB2C3 /// 未加载:https://app.gogenex.com/download String get _inviteLink => _info != null ? '$_baseInviteUrl?ref=${_info!.referralCode}' : _baseInviteUrl; // ── 生命周期 ───────────────────────────────────────────────────────────── @override void initState() { super.initState(); _loadInfo(); } // ── 数据加载 ───────────────────────────────────────────────────────────── /// 从 referral-service 拉取当前用户的推荐信息 /// /// 成功:更新 [_info],隐藏 loading /// 失败:记录 [_error],显示重试界面 Future _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; }); } } // ── 用户操作 ───────────────────────────────────────────────────────────── /// 复制成功后展示浮动 SnackBar(紫色背景 + 勾号图标) 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), ), ); } /// 复制推荐码到剪贴板 /// /// 示例复制内容:GNXAB2C3 Future _copyCode() async { if (_info == null) return; await Clipboard.setData(ClipboardData(text: _info!.referralCode)); if (mounted) _showCopied(context.t('share.codeCopied')); } /// 复制完整邀请链接到剪贴板 /// /// 示例:https://app.gogenex.com/download?ref=GNXAB2C3 Future _copyLink() async { await Clipboard.setData(ClipboardData(text: _inviteLink)); if (mounted) _showCopied(context.t('share.linkCopied')); } /// 调用系统原生分享弹层 /// /// 分享内容:多行文案(包含推荐码 + 邀请链接) /// /// iOS → Share Sheet(AirDrop、微信、邮件、短信……) /// Android → Sharesheet(微信、WhatsApp、Telegram……) /// /// 分享文案模板(zh-CN): /// 我在用 Genex 玩数字券金融!使用我的推荐码 {code} 注册,立享专属福利。 /// 下载链接:https://app.gogenex.com/download?ref={code} Future _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')); } // ── 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 ? _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(), // QR 码 + 推荐码 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:二维码 + 推荐码 ──────────────────────────────────────────── // // 视觉层次(由外到内): // Container[渐变背景 + 圆角20 + 阴影] // └── Column // ├── 标题行(星形图标 + "扫码下载 Genex") // ├── "Genex" 大字(字间距 3,FontWeight.w800) // ├── Container[白色背景] → QrImageView(180×180,紫色点阵) // └── 推荐码胶囊(半透明白底 + 复制按钮) // // QR 码内容:_inviteLink(如 https://app.gogenex.com/download?ref=GNXAB2C3) // QR 码颜色:eye=深紫 #4834D4,module=主紫 #6C5CE7,背景=白色 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), // ── 二维码 ────────────────────────────────────────────────────── // 白色衬底卡片包裹 QrImageView,确保二维码在任何背景下均可扫描 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, // 编码完整邀请 URL 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: [ // 推荐码文字:等宽字体 + 字间距 4,视觉更清晰 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, ), ), ], ), ), ], ), ), ), ], ), ), ); } // ── 邀请进度统计卡片 ───────────────────────────────────────────────────── // // 布局:白色圆角卡片,水平等分两格,中间细竖线分隔 // 左:直接推荐人数(Icons.people) // 右:团队总人数(Icons.groups) // // 数据来源:ReferralInfo.directReferralCount / totalTeamCount // 加载前显示 0,刷新后实时更新 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)), ], ); } // ── 分享操作列表 ───────────────────────────────────────────────────────── // // 白色圆角卡片,ListTile 列表(Divider 分隔): // // [紫色图标] 复制推荐码 副文本:GNXAB2C3 > // [蓝色图标] 复制链接 副文本:https://... > // [绿色图标] 分享给好友 副文本:通过微信/WhatsApp > // // 每项点击行为: // 复制推荐码 → _copyCode() → 剪贴板 + SnackBar // 复制链接 → _copyLink() → 剪贴板 + SnackBar // 分享给好友 → _shareNative() → 系统原生分享弹层 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, ), ], ), ), ], ); } /// 操作列表项 /// /// - [iconBg] 图标容器背景色(使用语义色的浅色版本) /// - [subtitle] 单行截断,防止链接/代码过长破坏布局 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, ); } }