import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show defaultTargetPlatform, TargetPlatform; import 'package:fluwx/fluwx.dart'; import 'package:tobias/tobias.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.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. 欢迎页 - 品牌展示 + 注册/登录入口 /// /// 平台差异(运行时判断): /// Android: 微信 + 支付宝 + Google /// iOS: 微信 + 支付宝 + Google + Apple class WelcomePage extends StatefulWidget { const WelcomePage({super.key}); @override State createState() => _WelcomePageState(); } class _WelcomePageState extends State { bool _wechatLoading = false; bool _alipayLoading = false; bool _googleLoading = false; bool _appleLoading = false; final _googleSignIn = GoogleSignIn(scopes: ['email', 'profile']); @override void initState() { super.initState(); // 监听微信授权回调(用户授权后,微信 App 返回 code) weChatResponseEventHandler.distinct().listen((res) { if (res is WXAuthResp && mounted) { _handleWechatAuthResp(res); } }); } // ── 微信 ───────────────────────────────────────────────────────────────── Future _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); await sendWeChatAuth(scope: 'snsapi_userinfo', state: 'genex_login'); // 授权结果通过 weChatResponseEventHandler 异步回调 } Future _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'))), ); } } } // ── 支付宝 ───────────────────────────────────────────────────────────────── // tobias 包使用方法: // isAliPayInstalled() → Future // aliPay(String orderStr) — 用于支付 // 但 OAuth 授权用的是 Alipay.authCode(appId, scope) 返回 auth_code // // tobias v3.x API: aliPayAuth(appId, scope) → Future // 返回 {'resultStatus': '9000', 'memo': '', 'result': '...(含 auth_code)...'} // resultStatus: 9000=成功, 6001=取消, 4000=错误 Future _onAlipayTap() async { final installed = await isAliPayInstalled; if (!installed) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.t('welcome.alipayNotInstalled'))), ); } return; } setState(() => _alipayLoading = true); try { // appId 与后端 ALIPAY_APP_ID 一致 const alipayAppId = String.fromEnvironment('ALIPAY_APP_ID', defaultValue: ''); final result = await aliPayAuth(alipayAppId, 'auth_user'); final status = result['resultStatus']?.toString() ?? ''; if (status != '9000') { // 用户取消或错误 return; } // 从 result 字符串中解析 auth_code // result 格式: "auth_code=AP_xxxxxxxx&scope=auth_user&state=..." final resultStr = result['result']?.toString() ?? ''; final authCode = _parseParam(resultStr, 'auth_code'); if (authCode == null || authCode.isEmpty) { throw Exception('未获取到 auth_code'); } await AuthService.instance.loginByAlipay(authCode: authCode); if (mounted) Navigator.pushReplacementNamed(context, '/main'); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.t('welcome.alipayLoginFailed'))), ); } } finally { if (mounted) setState(() => _alipayLoading = false); } } /// 从 URL Query 格式字符串中提取参数值 String? _parseParam(String str, String key) { final uri = Uri(query: str); return uri.queryParameters[key]; } // ── Google ───────────────────────────────────────────────────────────────── Future _onGoogleTap() async { setState(() => _googleLoading = true); try { final account = await _googleSignIn.signIn(); if (account == null) return; // 用户取消 final auth = await account.authentication; final idToken = auth.idToken; if (idToken == null) throw Exception('未获取到 ID Token'); await AuthService.instance.loginByGoogle(idToken: idToken); if (mounted) Navigator.pushReplacementNamed(context, '/main'); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.t('welcome.googleLoginFailed'))), ); } } finally { if (mounted) setState(() => _googleLoading = false); } } // ── Apple(仅 iOS)──────────────────────────────────────────────────────── Future _onAppleTap() async { setState(() => _appleLoading = true); try { final credential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], ); final identityToken = credential.identityToken; if (identityToken == null) throw Exception('未获取到 Identity Token'); // 拼接用户显示名(Apple 首次登录时提供,之后为 null) final displayName = [ credential.givenName, credential.familyName, ].where((s) => s != null && s.isNotEmpty).join(' '); await AuthService.instance.loginByApple( identityToken: identityToken, displayName: displayName.isNotEmpty ? displayName : null, ); if (mounted) Navigator.pushReplacementNamed(context, '/main'); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.t('welcome.appleLoginFailed'))), ); } } finally { if (mounted) setState(() => _appleLoading = false); } } // ── Build ───────────────────────────────────────────────────────────────── @override Widget build(BuildContext context) { final isIos = defaultTargetPlatform == TargetPlatform.iOS; 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 // Android: 微信 + 支付宝 + Google // iOS: 微信 + 支付宝 + 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: 20), _SocialLoginButton( // 支付宝官方蓝色 icon: Icons.account_balance_wallet_outlined, label: context.t('welcome.alipay'), color: const Color(0xFF1677FF), loading: _alipayLoading, onTap: _onAlipayTap, ), const SizedBox(width: 20), _SocialLoginButton( icon: Icons.g_mobiledata_rounded, label: 'Google', loading: _googleLoading, onTap: _onGoogleTap, ), if (isIos) ...[ const SizedBox(width: 20), _SocialLoginButton( icon: Icons.apple_rounded, label: 'Apple', loading: _appleLoading, onTap: _onAppleTap, ), ], ], ), 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), ], ), ); } }