275 lines
8.9 KiB
Dart
275 lines
8.9 KiB
Dart
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),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|