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

459 lines
15 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.

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,
);
}
}