feat(genex-mobile): 邀请好友 — 分享二维码页面全链路实现
- 新增 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 <noreply@anthropic.com>
This commit is contained in:
parent
3ae5e2f982
commit
46d2404d19
|
|
@ -783,6 +783,27 @@ const Map<String, String> 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',
|
||||
|
|
|
|||
|
|
@ -784,6 +784,27 @@ const Map<String, String> 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': 'アクティビティ',
|
||||
|
|
|
|||
|
|
@ -784,6 +784,27 @@ const Map<String, String> 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': '活动通知',
|
||||
|
|
|
|||
|
|
@ -784,6 +784,27 @@ const Map<String, String> 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': '活動通知',
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> 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<ReferralInfo> getMyInfo() async {
|
||||
final resp = await _api.get('/api/v1/referral/my');
|
||||
final data = resp.data['data'] as Map<String, dynamic>;
|
||||
return ReferralInfo.fromJson(data);
|
||||
}
|
||||
|
||||
/// 获取直接推荐列表
|
||||
Future<List<Map<String, dynamic>>> 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<String, dynamic>;
|
||||
return (data['items'] as List).cast<Map<String, dynamic>>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -179,6 +180,8 @@ class _GenexConsumerAppState extends State<GenexConsumerApp> {
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue