gcx/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart

275 lines
8.9 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:fluwx/fluwx.dart';
import '../../../../app/theme/app_colors.dart';
import '../../../../app/theme/app_typography.dart';
import '../../../../app/theme/app_spacing.dart';
import '../../../../shared/widgets/genex_button.dart';
import '../../../../app/i18n/app_localizations.dart';
import '../../../../core/services/auth_service.dart';
/// A1. 欢迎页 - 品牌展示 + 注册/登录入口
///
/// 品牌Logo、Slogan、手机号注册、邮箱注册、社交登录入口WeChat/Google/Apple
class WelcomePage extends StatefulWidget {
const WelcomePage({super.key});
@override
State<WelcomePage> createState() => _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage> {
bool _wechatLoading = false;
@override
void initState() {
super.initState();
// 监听微信授权回调(用户授权后,微信 App 返回 code
weChatResponseEventHandler.distinct().listen((res) {
if (res is WXAuthResp && mounted) {
_handleWechatAuthResp(res);
}
});
}
Future<void> _onWechatTap() async {
final installed = await isWeChatInstalled;
if (!installed) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.t('welcome.wechatNotInstalled'))),
);
}
return;
}
setState(() => _wechatLoading = true);
// 发起微信授权scope = snsapi_userinfo 可获取用户信息(昵称、头像)
await sendWeChatAuth(scope: 'snsapi_userinfo', state: 'genex_login');
// 授权结果通过 weChatResponseEventHandler 异步回调
}
Future<void> _handleWechatAuthResp(WXAuthResp resp) async {
setState(() => _wechatLoading = false);
if (resp.errCode != 0 || resp.code == null) {
// 用户取消授权或授权失败,不做任何处理
return;
}
try {
await AuthService.instance.loginByWechat(code: resp.code!);
if (mounted) {
Navigator.pushReplacementNamed(context, '/main');
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.t('welcome.wechatLoginFailed'))),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
const Spacer(flex: 2),
// Brand Logo
ClipRRect(
borderRadius: AppSpacing.borderRadiusXl,
child: Image.asset(
'assets/images/logo_icon.png',
width: 80,
height: 80,
),
),
const SizedBox(height: 12),
// Brand Name — 遵循 lockup 设计: "GEN" 深色 + "EX" 渐变
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'GEN',
style: AppTypography.displayLarge.copyWith(
color: const Color(0xFF1A103A),
fontWeight: FontWeight.w800,
letterSpacing: -0.3,
),
),
ShaderMask(
shaderCallback: (bounds) => const LinearGradient(
colors: [Color(0xFF9B8FFF), Color(0xFFB8ADFF)],
).createShader(bounds),
child: Text(
'EX',
style: AppTypography.displayLarge.copyWith(
color: Colors.white,
fontWeight: FontWeight.w800,
letterSpacing: -0.3,
),
),
),
],
),
const SizedBox(height: 6),
// Slogan
Text(
context.t('welcome.slogan'),
style: AppTypography.bodyLarge.copyWith(
color: AppColors.textSecondary,
),
),
const Spacer(flex: 3),
// Phone Register
GenexButton(
label: context.t('welcome.phoneRegister'),
icon: Icons.phone_android_rounded,
onPressed: () {
Navigator.pushNamed(context, '/register');
},
),
const SizedBox(height: 12),
// Email Register
GenexButton(
label: context.t('welcome.emailRegister'),
icon: Icons.email_outlined,
variant: GenexButtonVariant.outline,
onPressed: () {
Navigator.pushNamed(context, '/register', arguments: {'isEmail': true});
},
),
const SizedBox(height: 24),
// Social Login Divider
Row(
children: [
const Expanded(child: Divider(color: AppColors.border)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(context.t('welcome.otherLogin'), style: AppTypography.caption),
),
const Expanded(child: Divider(color: AppColors.border)),
],
),
const SizedBox(height: 16),
// Social Login Buttons — WeChat + Google + Apple
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_SocialLoginButton(
icon: Icons.wechat,
label: context.t('welcome.wechat'),
color: const Color(0xFF07C160),
loading: _wechatLoading,
onTap: _onWechatTap,
),
const SizedBox(width: 24),
_SocialLoginButton(
icon: Icons.g_mobiledata_rounded,
label: 'Google',
onTap: () {
Navigator.pushReplacementNamed(context, '/main');
},
),
const SizedBox(width: 24),
_SocialLoginButton(
icon: Icons.apple_rounded,
label: 'Apple',
onTap: () {
Navigator.pushReplacementNamed(context, '/main');
},
),
],
),
const SizedBox(height: 32),
// Already have account
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(context.t('welcome.hasAccount'), style: AppTypography.bodyMedium.copyWith(
color: AppColors.textSecondary,
)),
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/login');
},
child: Text(context.t('welcome.login'), style: AppTypography.labelMedium.copyWith(
color: AppColors.primary,
)),
),
],
),
const SizedBox(height: 16),
// Terms
Text(
context.t('welcome.agreement'),
style: AppTypography.caption.copyWith(fontSize: 10),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
],
),
),
),
);
}
}
class _SocialLoginButton extends StatelessWidget {
final IconData icon;
final String label;
final Color? color;
final VoidCallback onTap;
final bool loading;
const _SocialLoginButton({
required this.icon,
required this.label,
required this.onTap,
this.color,
this.loading = false,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: loading ? null : onTap,
child: Column(
children: [
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
color: color != null ? color!.withValues(alpha: 0.1) : AppColors.gray50,
shape: BoxShape.circle,
border: Border.all(color: color?.withValues(alpha: 0.3) ?? AppColors.border),
),
child: loading
? Padding(
padding: const EdgeInsets.all(14),
child: CircularProgressIndicator(
strokeWidth: 2,
color: color ?? AppColors.textPrimary,
),
)
: Icon(icon, size: 28, color: color ?? AppColors.textPrimary),
),
const SizedBox(height: 6),
Text(label, style: AppTypography.caption),
],
),
);
}
}