From 46d2404d19b58e4772a2c3c83fa1263ad21dddf9 Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 4 Mar 2026 01:28:22 -0800 Subject: [PATCH] =?UTF-8?q?feat(genex-mobile):=20=E9=82=80=E8=AF=B7?= =?UTF-8?q?=E5=A5=BD=E5=8F=8B=20=E2=80=94=20=E5=88=86=E4=BA=AB=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=E9=A1=B5=E9=9D=A2=E5=85=A8=E9=93=BE=E8=B7=AF?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SharePage:推荐码 QR 码 + 邀请进度 + 一键复制/原生分享 - ProfilePage 添加「邀请好友」渐变横幅入口 - 新增 ReferralService(getMyInfo / getDirectReferrals) - pubspec.yaml 引入 qr_flutter ^4.1.0、share_plus ^10.0.2 - 路由 /share 注册 - i18n:4 语言新增 share.* 共 20 个翻译键(zh-CN / zh-TW / en / ja) Co-Authored-By: Claude Sonnet 4.6 --- .../genex-mobile/lib/app/i18n/strings/en.dart | 21 + .../genex-mobile/lib/app/i18n/strings/ja.dart | 21 + .../lib/app/i18n/strings/zh_cn.dart | 21 + .../lib/app/i18n/strings/zh_tw.dart | 21 + .../lib/core/services/referral_service.dart | 57 +++ .../presentation/pages/profile_page.dart | 64 +++ .../presentation/pages/share_page.dart | 458 ++++++++++++++++++ frontend/genex-mobile/lib/main.dart | 3 + frontend/genex-mobile/pubspec.yaml | 2 + 9 files changed, 668 insertions(+) create mode 100644 frontend/genex-mobile/lib/core/services/referral_service.dart create mode 100644 frontend/genex-mobile/lib/features/profile/presentation/pages/share_page.dart diff --git a/frontend/genex-mobile/lib/app/i18n/strings/en.dart b/frontend/genex-mobile/lib/app/i18n/strings/en.dart index 0b1f945..13e46f6 100644 --- a/frontend/genex-mobile/lib/app/i18n/strings/en.dart +++ b/frontend/genex-mobile/lib/app/i18n/strings/en.dart @@ -783,6 +783,27 @@ const Map en = { 'update.isLatest': 'Already up to date', 'update.checkUpdate': 'Check for Updates', + // ============ Share / Invite ============ + 'share.title': 'Invite Friends', + 'share.scanToJoin': 'Scan to Download Genex', + 'share.myReferralCode': 'My Referral Code', + 'share.copyCode': 'Copy Code', + 'share.copyLink': 'Copy Link', + 'share.shareToFriend': 'Share with Friends', + 'share.directReferrals': 'Direct Referrals', + 'share.teamSize': 'Team Size', + 'share.quickShare': 'Share Via', + 'share.codeCopied': 'Referral Code Copied', + 'share.linkCopied': 'Link Copied', + 'share.shareText': 'Join me on Genex, the digital coupon finance platform!\nUse my referral code {code} when signing up for exclusive rewards.\nDownload here: {link}', + 'share.nativeShareTitle': 'Share Genex', + 'share.shareSubtitle': 'Share via WeChat, WhatsApp, or more', + 'share.loading': 'Loading referral info...', + 'share.loadFailed': 'Failed to load, please retry', + 'share.retry': 'Retry', + 'share.inviteBanner': 'Invite Friends', + 'share.inviteBannerSub': 'Earn rewards for every referral', + // ============ Notification ============ 'notification.system': 'System', 'notification.activity': 'Activity', diff --git a/frontend/genex-mobile/lib/app/i18n/strings/ja.dart b/frontend/genex-mobile/lib/app/i18n/strings/ja.dart index 755cd2d..ab65e34 100644 --- a/frontend/genex-mobile/lib/app/i18n/strings/ja.dart +++ b/frontend/genex-mobile/lib/app/i18n/strings/ja.dart @@ -784,6 +784,27 @@ const Map ja = { 'update.isLatest': '最新バージョンです', 'update.checkUpdate': 'アップデート確認', + // ============ Share / Invite ============ + 'share.title': '友達を招待', + 'share.scanToJoin': 'スキャンしてGenexをダウンロード', + 'share.myReferralCode': 'マイ招待コード', + 'share.copyCode': 'コードをコピー', + 'share.copyLink': 'リンクをコピー', + 'share.shareToFriend': '友達にシェア', + 'share.directReferrals': '直接招待', + 'share.teamSize': 'チーム人数', + 'share.quickShare': 'シェア方法', + 'share.codeCopied': '招待コードをコピーしました', + 'share.linkCopied': 'リンクをコピーしました', + 'share.shareText': 'GenexでデジタルクーポンFinanceを楽しもう!\n招待コード {code} で登録すると特典があります。\nダウンロード:{link}', + 'share.nativeShareTitle': 'Genexをシェア', + 'share.shareSubtitle': 'WeChat・WhatsApp等でシェア', + 'share.loading': '招待情報を読み込み中...', + 'share.loadFailed': '読み込みに失敗しました。再試行してください', + 'share.retry': '再試行', + 'share.inviteBanner': '友達を招待', + 'share.inviteBannerSub': '友達を招待してポイントをゲット', + // ============ Notification ============ 'notification.system': 'システム', 'notification.activity': 'アクティビティ', diff --git a/frontend/genex-mobile/lib/app/i18n/strings/zh_cn.dart b/frontend/genex-mobile/lib/app/i18n/strings/zh_cn.dart index a5c146f..2499e99 100644 --- a/frontend/genex-mobile/lib/app/i18n/strings/zh_cn.dart +++ b/frontend/genex-mobile/lib/app/i18n/strings/zh_cn.dart @@ -784,6 +784,27 @@ const Map zhCN = { 'update.isLatest': '当前已是最新版本', 'update.checkUpdate': '检查更新', + // ============ Share / Invite ============ + 'share.title': '邀请好友', + 'share.scanToJoin': '扫码下载 Genex', + 'share.myReferralCode': '我的专属推荐码', + 'share.copyCode': '复制推荐码', + 'share.copyLink': '复制链接', + 'share.shareToFriend': '分享给好友', + 'share.directReferrals': '直接推荐', + 'share.teamSize': '团队人数', + 'share.quickShare': '分享方式', + 'share.codeCopied': '推荐码已复制', + 'share.linkCopied': '链接已复制', + 'share.shareText': '我在用 Genex 玩数字券金融!使用我的推荐码 {code} 注册,立享专属福利。\n下载链接:{link}', + 'share.nativeShareTitle': '分享 Genex', + 'share.shareSubtitle': '通过微信、WhatsApp 或其他方式分享', + 'share.loading': '正在加载推荐信息...', + 'share.loadFailed': '加载失败,请重试', + 'share.retry': '重试', + 'share.inviteBanner': '邀请好友', + 'share.inviteBannerSub': '推荐好友注册,双方均享福利', + // ============ Notification ============ 'notification.system': '系统通知', 'notification.activity': '活动通知', diff --git a/frontend/genex-mobile/lib/app/i18n/strings/zh_tw.dart b/frontend/genex-mobile/lib/app/i18n/strings/zh_tw.dart index 7696cdb..37791d2 100644 --- a/frontend/genex-mobile/lib/app/i18n/strings/zh_tw.dart +++ b/frontend/genex-mobile/lib/app/i18n/strings/zh_tw.dart @@ -784,6 +784,27 @@ const Map zhTW = { 'update.isLatest': '當前已是最新版本', 'update.checkUpdate': '檢查更新', + // ============ Share / Invite ============ + 'share.title': '邀請好友', + 'share.scanToJoin': '掃碼下載 Genex', + 'share.myReferralCode': '我的專屬推薦碼', + 'share.copyCode': '複製推薦碼', + 'share.copyLink': '複製連結', + 'share.shareToFriend': '分享給好友', + 'share.directReferrals': '直接推薦', + 'share.teamSize': '團隊人數', + 'share.quickShare': '分享方式', + 'share.codeCopied': '推薦碼已複製', + 'share.linkCopied': '連結已複製', + 'share.shareText': '我在用 Genex 玩數位券金融!使用我的推薦碼 {code} 註冊,立享專屬福利。\n下載連結:{link}', + 'share.nativeShareTitle': '分享 Genex', + 'share.shareSubtitle': '透過微信、WhatsApp 或其他方式分享', + 'share.loading': '正在載入推薦資訊...', + 'share.loadFailed': '載入失敗,請重試', + 'share.retry': '重試', + 'share.inviteBanner': '邀請好友', + 'share.inviteBannerSub': '推薦好友註冊,雙方均享福利', + // ============ Notification ============ 'notification.system': '系統通知', 'notification.activity': '活動通知', diff --git a/frontend/genex-mobile/lib/core/services/referral_service.dart b/frontend/genex-mobile/lib/core/services/referral_service.dart new file mode 100644 index 0000000..822c0d1 --- /dev/null +++ b/frontend/genex-mobile/lib/core/services/referral_service.dart @@ -0,0 +1,57 @@ +import '../network/api_client.dart'; + +/// 推荐关系信息 +class ReferralInfo { + final String referralCode; + final int directReferralCount; + final int totalTeamCount; + final String? referrerId; + final String? usedCode; + + ReferralInfo({ + required this.referralCode, + required this.directReferralCount, + required this.totalTeamCount, + this.referrerId, + this.usedCode, + }); + + factory ReferralInfo.fromJson(Map json) { + return ReferralInfo( + referralCode: json['referralCode'] as String, + directReferralCount: json['directReferralCount'] as int? ?? 0, + totalTeamCount: json['totalTeamCount'] as int? ?? 0, + referrerId: json['referrerId'] as String?, + usedCode: json['usedCode'] as String?, + ); + } +} + +/// Referral Service — 对接 referral-service API +class ReferralService { + static final ReferralService _instance = ReferralService._(); + static ReferralService get instance => _instance; + ReferralService._(); + + final _api = ApiClient.instance; + + /// 获取当前用户的推荐信息(需登录) + Future getMyInfo() async { + final resp = await _api.get('/api/v1/referral/my'); + final data = resp.data['data'] as Map; + return ReferralInfo.fromJson(data); + } + + /// 获取直接推荐列表 + Future>> getDirectReferrals({ + int offset = 0, + int limit = 20, + }) async { + final resp = await _api.get('/api/v1/referral/direct', queryParameters: { + 'offset': offset, + 'limit': limit, + }); + final data = resp.data['data'] as Map; + return (data['items'] as List).cast>(); + } +} diff --git a/frontend/genex-mobile/lib/features/profile/presentation/pages/profile_page.dart b/frontend/genex-mobile/lib/features/profile/presentation/pages/profile_page.dart index a848f0d..fae9064 100644 --- a/frontend/genex-mobile/lib/features/profile/presentation/pages/profile_page.dart +++ b/frontend/genex-mobile/lib/features/profile/presentation/pages/profile_page.dart @@ -23,6 +23,9 @@ class ProfilePage extends StatelessWidget { // Quick Stats SliverToBoxAdapter(child: _buildQuickStats(context)), + // Invite Banner + SliverToBoxAdapter(child: _buildInviteBanner(context)), + // Menu Sections SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.account'), [ _MenuItem(Icons.verified_user_outlined, context.t('profile.kyc'), '${context.t('kyc.completed')} L1', true, @@ -171,6 +174,67 @@ class ProfilePage extends StatelessWidget { ); } + Widget _buildInviteBanner(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 12, 20, 0), + child: GestureDetector( + onTap: () => Navigator.pushNamed(context, '/share'), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [Color(0xFF6C5CE7), Color(0xFF9B8FFF)], + ), + borderRadius: AppSpacing.borderRadiusMd, + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.25), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + color: Colors.white24, + shape: BoxShape.circle, + ), + child: const Icon(Icons.card_giftcard_rounded, color: Colors.white, size: 22), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.t('share.inviteBanner'), + style: AppTypography.labelMedium.copyWith( + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 2), + Text( + context.t('share.inviteBannerSub'), + style: AppTypography.caption.copyWith(color: Colors.white70), + ), + ], + ), + ), + const Icon(Icons.chevron_right_rounded, color: Colors.white70, size: 24), + ], + ), + ), + ), + ); + } + Widget _buildMenuSection(String title, List<_MenuItem> items) { return Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), diff --git a/frontend/genex-mobile/lib/features/profile/presentation/pages/share_page.dart b/frontend/genex-mobile/lib/features/profile/presentation/pages/share_page.dart new file mode 100644 index 0000000..202e0ef --- /dev/null +++ b/frontend/genex-mobile/lib/features/profile/presentation/pages/share_page.dart @@ -0,0 +1,458 @@ +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 createState() => _SharePageState(); +} + +class _SharePageState extends State { + 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 _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 _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: _inviteLink)); + if (mounted) _showCopied(context.t('share.linkCopied')); + } + + 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')); + } + + @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, + ); + } +} diff --git a/frontend/genex-mobile/lib/main.dart b/frontend/genex-mobile/lib/main.dart index 5df7989..0b52d4b 100644 --- a/frontend/genex-mobile/lib/main.dart +++ b/frontend/genex-mobile/lib/main.dart @@ -36,6 +36,7 @@ import 'features/issuer/presentation/pages/issuer_main_page.dart'; import 'features/merchant/presentation/pages/merchant_home_page.dart'; import 'features/trading/presentation/pages/trading_detail_page.dart'; import 'features/coupons/presentation/pages/wallet_coupons_page.dart'; +import 'features/profile/presentation/pages/share_page.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -179,6 +180,8 @@ class _GenexConsumerAppState extends State { return MaterialPageRoute(builder: (_) => const TradingDetailPage()); case '/wallet/coupons': return MaterialPageRoute(builder: (_) => const WalletCouponsPage()); + case '/share': + return MaterialPageRoute(builder: (_) => const SharePage()); default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/frontend/genex-mobile/pubspec.yaml b/frontend/genex-mobile/pubspec.yaml index 35b6aa1..66a9c5f 100644 --- a/frontend/genex-mobile/pubspec.yaml +++ b/frontend/genex-mobile/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: flutter_local_notifications: ^18.0.0 in_app_update: ^4.2.2 shared_preferences: ^2.2.3 + qr_flutter: ^4.1.0 + share_plus: ^10.0.2 dev_dependencies: flutter_test: